fix(chat): don't wipe task-bound planner chat history on /new or /clear

With showTaskChatsInCommonFeed enabled, task-planner sessions appear in the
common Direct feed, so /new//clear there hit ChatView's generic intercept and
createSession orphaned the task's planner history behind a fresh session.
Consume the command with a warning toast for task-bound sessions instead.

Also updates 6 stale ChatView.core.test.tsx sendMessage assertions broken by
FN-8502's onDelivered/onFailed options argument.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-23 21:58:21 -07:00
parent 049c36c9ae
commit 0dd34cd306
11 changed files with 226 additions and 28 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: /new and /clear in Chat no longer wipe a task-bound planner chat's history.
category: fix
dev: ChatView's exact `/new`//`/clear` intercept now recognizes `task-planner:<taskId>` sessions (surfaced in the common feed via `showTaskChatsInCommonFeed`) and consumes the command with a warning toast instead of calling createSession.

View File

@@ -22,7 +22,7 @@ import {
PinOff, PinOff,
MoreHorizontal, MoreHorizontal,
} from "lucide-react"; } from "lucide-react";
import { FN_AGENT_ID, useChat, type ChatMessageInfo } from "../hooks/useChat"; import { FN_AGENT_ID, TASK_PLANNER_CHAT_AGENT_ID_PREFIX, useChat, type ChatMessageInfo } from "../hooks/useChat";
import { RoomMessageDeliveredButReplyFailedError, useChatRooms } from "../hooks/useChatRooms"; import { RoomMessageDeliveredButReplyFailedError, useChatRooms } from "../hooks/useChatRooms";
import { useChatUnread } from "../hooks/useChatUnread"; import { useChatUnread } from "../hooks/useChatUnread";
import { useViewportMode } from "./Header"; import { useViewportMode } from "./Header";
@@ -1854,6 +1854,18 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
} }
if (trimmed === "/clear" || trimmed === "/new") { if (trimmed === "/clear" || trimmed === "/new") {
/*
FNXC:ChatSlashCommands 2026-07-23-12:00:
`/new`//`/clear` must never wipe a task-bound planner chat. With `showTaskChatsInCommonFeed`
enabled, task-planner sessions appear in the common Direct feed, so a user can run `/new`
against one directly — but that transcript is the task's planner history, and createSession
would orphan it behind a fresh session. Consume the command with feedback instead of clearing.
*/
if (activeSession.agentId.startsWith(TASK_PLANNER_CHAT_AGENT_ID_PREFIX)) {
clearComposerState();
addToast(t("chat.newNotAllowedForTaskChat", "This chat is tied to a task — /new and /clear can't clear it"), "warning");
return;
}
clearComposerState(); clearComposerState();
clearPendingMessage(); clearPendingMessage();
stopStreaming(); stopStreaming();

View File

@@ -1300,6 +1300,41 @@ describe("ChatView", () => {
expect(clearPendingMessage).toHaveBeenCalledTimes(1); expect(clearPendingMessage).toHaveBeenCalledTimes(1);
}); });
/*
FNXC:ChatSlashCommands 2026-07-23-12:00:
With `showTaskChatsInCommonFeed` enabled, task-planner sessions (`task-planner:<taskId>`)
are selectable in the common Chat feed. `/new`//`/clear` there must not replace the session —
the transcript is the task's planner history. Invariant covers both command spellings.
*/
it.each(["/new", "/clear"])("does not clear a task-bound planner chat on %s", async (command) => {
const sendMessage = vi.fn();
const createSession = vi.fn();
const stopStreaming = vi.fn();
const clearPendingMessage = vi.fn();
const addToast = vi.fn();
setupMockChat({
activeSession: { ...activeSessionFixture, agentId: "task-planner:task-123" },
messages: [],
sendMessage,
createSession,
stopStreaming,
clearPendingMessage,
});
await renderWithAct(<ChatView projectId="proj-123" addToast={addToast} />);
const textarea = screen.getByTestId("chat-input");
await userEvent.type(textarea, ` ${command} {enter}`);
expect(sendMessage).not.toHaveBeenCalled();
expect(createSession).not.toHaveBeenCalled();
expect(stopStreaming).not.toHaveBeenCalled();
expect(clearPendingMessage).not.toHaveBeenCalled();
expect(addToast).toHaveBeenCalledWith(expect.stringContaining("tied to a task"), "warning");
expect(textarea).toHaveValue("");
});
it("does not intercept non-exact /new text", async () => { it("does not intercept non-exact /new text", async () => {
const sendMessage = vi.fn(); const sendMessage = vi.fn();
const createSession = vi.fn(); const createSession = vi.fn();
@@ -1315,7 +1350,7 @@ describe("ChatView", () => {
const textarea = screen.getByTestId("chat-input"); const textarea = screen.getByTestId("chat-input");
await userEvent.type(textarea, "/new now{enter}"); await userEvent.type(textarea, "/new now{enter}");
expect(sendMessage).toHaveBeenCalledWith("/new now", []); expect(sendMessage).toHaveBeenCalledWith("/new now", [], expect.objectContaining({ onDelivered: expect.any(Function), onFailed: expect.any(Function) }));
expect(createSession).not.toHaveBeenCalled(); expect(createSession).not.toHaveBeenCalled();
}); });
@@ -1334,7 +1369,7 @@ describe("ChatView", () => {
const textarea = screen.getByTestId("chat-input"); const textarea = screen.getByTestId("chat-input");
await userEvent.type(textarea, "/clear now{enter}"); await userEvent.type(textarea, "/clear now{enter}");
expect(sendMessage).toHaveBeenCalledWith("/clear now", []); expect(sendMessage).toHaveBeenCalledWith("/clear now", [], expect.objectContaining({ onDelivered: expect.any(Function), onFailed: expect.any(Function) }));
expect(createSession).not.toHaveBeenCalled(); expect(createSession).not.toHaveBeenCalled();
}); });
@@ -1351,7 +1386,7 @@ describe("ChatView", () => {
const textarea = screen.getByTestId("chat-input"); const textarea = screen.getByTestId("chat-input");
await userEvent.type(textarea, "Hello world{enter}"); await userEvent.type(textarea, "Hello world{enter}");
expect(sendMessage).toHaveBeenCalledWith("Hello world", []); expect(sendMessage).toHaveBeenCalledWith("Hello world", [], expect.objectContaining({ onDelivered: expect.any(Function), onFailed: expect.any(Function) }));
}); });
it("sends message on touch tap when the synthetic click is suppressed (mobile)", async () => { it("sends message on touch tap when the synthetic click is suppressed (mobile)", async () => {
@@ -1381,7 +1416,7 @@ describe("ChatView", () => {
}); });
expect(sendMessage).toHaveBeenCalledTimes(1); expect(sendMessage).toHaveBeenCalledTimes(1);
expect(sendMessage).toHaveBeenCalledWith("Touch hello", []); expect(sendMessage).toHaveBeenCalledWith("Touch hello", [], expect.objectContaining({ onDelivered: expect.any(Function), onFailed: expect.any(Function) }));
} finally { } finally {
Object.defineProperty(window, "innerWidth", { value: originalInnerWidth, configurable: true }); Object.defineProperty(window, "innerWidth", { value: originalInnerWidth, configurable: true });
} }
@@ -1404,7 +1439,7 @@ describe("ChatView", () => {
fireEvent.pointerDown(screen.getByTestId("chat-send-btn"), { pointerType: "touch" }); fireEvent.pointerDown(screen.getByTestId("chat-send-btn"), { pointerType: "touch" });
}); });
expect(sendMessage).toHaveBeenCalledTimes(1); expect(sendMessage).toHaveBeenCalledTimes(1);
expect(sendMessage).toHaveBeenLastCalledWith("Direct first", []); expect(sendMessage).toHaveBeenLastCalledWith("Direct first", [], expect.objectContaining({ onDelivered: expect.any(Function), onFailed: expect.any(Function) }));
await act(async () => { await act(async () => {
await new Promise((resolve) => window.setTimeout(resolve, 0)); await new Promise((resolve) => window.setTimeout(resolve, 0));
}); });
@@ -1415,7 +1450,7 @@ describe("ChatView", () => {
}); });
expect(sendMessage).toHaveBeenCalledTimes(2); expect(sendMessage).toHaveBeenCalledTimes(2);
expect(sendMessage).toHaveBeenLastCalledWith("Direct second", []); expect(sendMessage).toHaveBeenLastCalledWith("Direct second", [], expect.objectContaining({ onDelivered: expect.any(Function), onFailed: expect.any(Function) }));
viewportSpy.mockRestore(); viewportSpy.mockRestore();
}); });
@@ -1531,7 +1566,7 @@ describe("ChatView", () => {
const textarea = screen.getByTestId("chat-input") as HTMLTextAreaElement; const textarea = screen.getByTestId("chat-input") as HTMLTextAreaElement;
await userEvent.type(textarea, "Direct hello{enter}"); await userEvent.type(textarea, "Direct hello{enter}");
expect(sendMessage).toHaveBeenCalledWith("Direct hello", []); expect(sendMessage).toHaveBeenCalledWith("Direct hello", [], expect.objectContaining({ onDelivered: expect.any(Function), onFailed: expect.any(Function) }));
expect(sendRoomMessage).not.toHaveBeenCalled(); expect(sendRoomMessage).not.toHaveBeenCalled();
expect(textarea.value).toBe(""); expect(textarea.value).toBe("");
localStorage.removeItem("fusion:chat-scope"); localStorage.removeItem("fusion:chat-scope");

View File

@@ -25,7 +25,15 @@ const ACTIVE_SESSION_STORAGE_KEY = "kb-chat-active-session";
* Model-loop direct sessions store this sentinel agent id so the UI and hook share one target-mode check instead of duplicating the literal in each composer surface. * Model-loop direct sessions store this sentinel agent id so the UI and hook share one target-mode check instead of duplicating the literal in each composer surface.
*/ */
export const FN_AGENT_ID = "__fn_agent__"; export const FN_AGENT_ID = "__fn_agent__";
const TASK_PLANNER_CHAT_AGENT_ID_PREFIX = "task-planner:"; /**
* FNXC:ChatSlashCommands 2026-07-23-12:00:
* Exported (as a primitive, which survives the test harness's useChat automock) so composer
* surfaces can recognize a task-bound planner session (`task-planner:<taskId>`). Task chats
* surfaced in the common Direct feed via `showTaskChatsInCommonFeed` must never be cleared or
* replaced by `/new`//`/clear` — the transcript IS the task's planner history, not a disposable
* direct conversation.
*/
export const TASK_PLANNER_CHAT_AGENT_ID_PREFIX = "task-planner:";
/** FNXC:ChatPinned 2026-07-16-12:00: one comparator keeps refresh, cache, /** FNXC:ChatPinned 2026-07-16-12:00: one comparator keeps refresh, cache,
* optimistic mutations, SSE updates, and search results pinned-first. */ * optimistic mutations, SSE updates, and search results pinned-first. */

View File

@@ -1306,6 +1306,7 @@
"newChatModeAgent": "Agent", "newChatModeAgent": "Agent",
"newChatModeModel": "Model", "newChatModeModel": "Model",
"newChatTitle": "New Chat", "newChatTitle": "New Chat",
"newNotAllowedForTaskChat": "This chat is tied to a task — /new and /clear can't clear it",
"noAgentsAvailable": "No agents available", "noAgentsAvailable": "No agents available",
"noConversationsYet": "No conversations yet", "noConversationsYet": "No conversations yet",
"noMessage": "No message", "noMessage": "No message",

View File

@@ -1387,7 +1387,8 @@
"noModelsAvailable": "No models available", "noModelsAvailable": "No models available",
"currentAgentTarget": "Current agent: {{name}}", "currentAgentTarget": "Current agent: {{name}}",
"currentModelTarget": "Current model: {{model}}", "currentModelTarget": "Current model: {{model}}",
"currentDefaultTarget": "Using the default chat target" "currentDefaultTarget": "Using the default chat target",
"newNotAllowedForTaskChat": ""
}, },
"chatRooms": { "chatRooms": {
"error": { "error": {

View File

@@ -1387,7 +1387,8 @@
"noModelsAvailable": "No models available", "noModelsAvailable": "No models available",
"currentAgentTarget": "Current agent: {{name}}", "currentAgentTarget": "Current agent: {{name}}",
"currentModelTarget": "Current model: {{model}}", "currentModelTarget": "Current model: {{model}}",
"currentDefaultTarget": "Using the default chat target" "currentDefaultTarget": "Using the default chat target",
"newNotAllowedForTaskChat": ""
}, },
"chatRooms": { "chatRooms": {
"error": { "error": {

View File

@@ -1387,7 +1387,8 @@
"noModelsAvailable": "No models available", "noModelsAvailable": "No models available",
"currentAgentTarget": "Current agent: {{name}}", "currentAgentTarget": "Current agent: {{name}}",
"currentModelTarget": "Current model: {{model}}", "currentModelTarget": "Current model: {{model}}",
"currentDefaultTarget": "Using the default chat target" "currentDefaultTarget": "Using the default chat target",
"newNotAllowedForTaskChat": ""
}, },
"chatRooms": { "chatRooms": {
"error": { "error": {

View File

@@ -1387,7 +1387,8 @@
"noModelsAvailable": "No models available", "noModelsAvailable": "No models available",
"currentAgentTarget": "Current agent: {{name}}", "currentAgentTarget": "Current agent: {{name}}",
"currentModelTarget": "Current model: {{model}}", "currentModelTarget": "Current model: {{model}}",
"currentDefaultTarget": "Using the default chat target" "currentDefaultTarget": "Using the default chat target",
"newNotAllowedForTaskChat": ""
}, },
"chatRooms": { "chatRooms": {
"error": { "error": {

View File

@@ -1387,7 +1387,8 @@
"noModelsAvailable": "No models available", "noModelsAvailable": "No models available",
"currentAgentTarget": "Current agent: {{name}}", "currentAgentTarget": "Current agent: {{name}}",
"currentModelTarget": "Current model: {{model}}", "currentModelTarget": "Current model: {{model}}",
"currentDefaultTarget": "Using the default chat target" "currentDefaultTarget": "Using the default chat target",
"newNotAllowedForTaskChat": ""
}, },
"chatRooms": { "chatRooms": {
"error": { "error": {

View File

@@ -359,6 +359,7 @@ export default interface Resources {
"bulkAgentActions": "Bulk agent actions", "bulkAgentActions": "Bulk agent actions",
"bulkConfirmMessage_one": "{{action}} {{count}} agent(s) in this project?", "bulkConfirmMessage_one": "{{action}} {{count}} agent(s) in this project?",
"bulkConfirmMessage_other": "{{action}} {{count}} agent(s) in this project?", "bulkConfirmMessage_other": "{{action}} {{count}} agent(s) in this project?",
"bulkFailures": "failed {{count}}",
"bulkNoEligible": "No eligible agents", "bulkNoEligible": "No eligible agents",
"bulkResultWithFailures": "{{action}} {{count}} agent(s), {{failed}} failed", "bulkResultWithFailures": "{{action}} {{count}} agent(s), {{failed}} failed",
"bulkResult_one": "{{action}} {{successCount}} {{agentWord}}; skipped {{skippedCount}}", "bulkResult_one": "{{action}} {{successCount}} {{agentWord}}; skipped {{skippedCount}}",
@@ -446,9 +447,19 @@ export default interface Resources {
"details": "Details", "details": "Details",
"dialogAriaLabel": "Create new agent", "dialogAriaLabel": "Create new agent",
"dialogTitle": "New Agent", "dialogTitle": "New Agent",
"disableAllHeartbeats": "Disable all heartbeats",
"disableAllHeartbeatsConfirm": "Disable heartbeats for {{count}} project agents?",
"disableHeartbeat": "Disable heartbeat",
"disableHeartbeatsCountHint": "Disable {{count}} enabled project heartbeats",
"disableHeartbeatsSummary": "Disabled {{count}} heartbeats; skipped {{skipped}}",
"employeesTitle": "Employees", "employeesTitle": "Employees",
"emptySubtitle": "Create an agent to get started", "emptySubtitle": "Create an agent to get started",
"emptyTitle": "No agents found", "emptyTitle": "No agents found",
"enableAllHeartbeats": "Enable all heartbeats",
"enableAllHeartbeatsConfirm": "Enable heartbeats for {{count}} project agents?",
"enableHeartbeat": "Enable heartbeat",
"enableHeartbeatsCountHint": "Enable {{count}} disabled project heartbeats",
"enableHeartbeatsSummary": "Enabled {{count}} heartbeats; skipped {{skipped}}",
"engineerBacklogAutoClaim": "Engineer Backlog Auto-Claim", "engineerBacklogAutoClaim": "Engineer Backlog Auto-Claim",
"engineerBacklogAutoClaimHint": "Per-agent override of the project default. Allows this engineer-role agent to auto-claim unowned backlog tasks; explicit assignment and delegation are unchanged.", "engineerBacklogAutoClaimHint": "Per-agent override of the project default. Allows this engineer-role agent to auto-claim unowned backlog tasks; explicit assignment and delegation are unchanged.",
"errors": "Errors", "errors": "Errors",
@@ -511,11 +522,13 @@ export default interface Resources {
}, },
"healthError": "Error", "healthError": "Error",
"heartbeat": "Heartbeat:", "heartbeat": "Heartbeat:",
"heartbeatActionForAgent": "{{action}} for {{name}}",
"heartbeatAndHealth": "Heartbeat & Health", "heartbeatAndHealth": "Heartbeat & Health",
"heartbeatClampedToMin_one": "Heartbeat interval set to 5 minutes (minimum). {{count}} minute was below the 5-minute minimum.", "heartbeatClampedToMin_one": "Heartbeat interval set to 5 minutes (minimum). {{count}} minute was below the 5-minute minimum.",
"heartbeatClampedToMin_other": "Heartbeat interval set to 5 minutes (minimum). {{count}} minutes was below the 5-minute minimum.", "heartbeatClampedToMin_other": "Heartbeat interval set to 5 minutes (minimum). {{count}} minutes was below the 5-minute minimum.",
"heartbeatCustom": "Custom heartbeat run", "heartbeatCustom": "Custom heartbeat run",
"heartbeatEnabled": "Heartbeat Enabled", "heartbeatEnabled": "Heartbeat Enabled",
"heartbeatEnabledForAgent": "Heartbeat enabled for {{name}}",
"heartbeatEnabledHint": "Allow this agent to run on a scheduled heartbeat.", "heartbeatEnabledHint": "Allow this agent to run on a scheduled heartbeat.",
"heartbeatEnterMinutes": "Please enter a heartbeat interval in minutes", "heartbeatEnterMinutes": "Please enter a heartbeat interval in minutes",
"heartbeatFileEditMode": "Edit mode", "heartbeatFileEditMode": "Edit mode",
@@ -557,6 +570,8 @@ export default interface Resources {
"heartbeatStarted": "Heartbeat started", "heartbeatStarted": "Heartbeat started",
"heartbeatTimeoutHint": "Maximum time in seconds a heartbeat run may take before being killed.", "heartbeatTimeoutHint": "Maximum time in seconds a heartbeat run may take before being killed.",
"heartbeatTimeoutLabel": "Heartbeat Timeout (s)", "heartbeatTimeoutLabel": "Heartbeat Timeout (s)",
"heartbeatUpdateFailed": "Failed to update heartbeat: {{error}}",
"heartbeatUpdating": "Updating heartbeat…",
"heartbeatUpgradeFailed": "Failed to upgrade heartbeat procedure", "heartbeatUpgradeFailed": "Failed to upgrade heartbeat procedure",
"iconLabel": "Icon", "iconLabel": "Icon",
"iconPlaceholder": "e.g. 🤖", "iconPlaceholder": "e.g. 🤖",
@@ -716,6 +731,8 @@ export default interface Resources {
"noCompaniesMatch": "No companies match your search", "noCompaniesMatch": "No companies match your search",
"noEmployees": "No employees", "noEmployees": "No employees",
"noEmployeesDesc": "This agent has no direct reports.", "noEmployeesDesc": "This agent has no direct reports.",
"noHeartbeatsToDisable": "No agent heartbeats need disabling",
"noHeartbeatsToEnable": "No agent heartbeats need enabling",
"noInboxMessages": "No messages in inbox", "noInboxMessages": "No messages in inbox",
"noInstructionsPath": "No instructions file path set", "noInstructionsPath": "No instructions file path set",
"noLimit": "No limit", "noLimit": "No limit",
@@ -1136,6 +1153,7 @@ export default interface Resources {
"backendError": { "backendError": {
"failedFetch": "Failed to fetch projects" "failedFetch": "Failed to fetch projects"
}, },
"migrationInProgress": "Database migration in progress — the dashboard will reconnect when it completes.",
"testMode": "Test mode — no real AI calls" "testMode": "Test mode — no real AI calls"
}, },
"appName": "Fusion", "appName": "Fusion",
@@ -1284,6 +1302,7 @@ export default interface Resources {
"newChatModeAgent": "Agent", "newChatModeAgent": "Agent",
"newChatModeModel": "Model", "newChatModeModel": "Model",
"newChatTitle": "New Chat", "newChatTitle": "New Chat",
"newNotAllowedForTaskChat": "This chat is tied to a task — /new and /clear can't clear it",
"noAgentsAvailable": "No agents available", "noAgentsAvailable": "No agents available",
"noConversationsYet": "No conversations yet", "noConversationsYet": "No conversations yet",
"noMessage": "No message", "noMessage": "No message",
@@ -1306,6 +1325,7 @@ export default interface Resources {
"questionTextPlaceholder": "Type your answer here…", "questionTextPlaceholder": "Type your answer here…",
"queuedMessage": "Queued: {{preview}}", "queuedMessage": "Queued: {{preview}}",
"quickChatTitle": "Quick Chat", "quickChatTitle": "Quick Chat",
"recent": "Recent",
"relativeTimeDays_one": "{{count}}d ago", "relativeTimeDays_one": "{{count}}d ago",
"relativeTimeDays_other": "{{count}}d ago", "relativeTimeDays_other": "{{count}}d ago",
"relativeTimeHours_one": "{{count}}h ago", "relativeTimeHours_one": "{{count}}h ago",
@@ -1682,6 +1702,30 @@ export default interface Resources {
"toolCategoriesHint": "Autonomous work grouped by tool family", "toolCategoriesHint": "Autonomous work grouped by tool family",
"uniqueModels": "Unique models" "uniqueModels": "Unique models"
}, },
"portability": {
"description": "Download or preview a secret-scrubbed organization bundle.",
"export": {
"action": "Export org bundle",
"error": "Export failed",
"success": "Export ready",
"working": "Exporting…"
},
"import": {
"apply": "Apply import",
"confirmApply": "Import bundle",
"confirmMessage": "This applies the previewed configuration to this project.",
"confirmTitle": "Import organization bundle?",
"error": "Unable to preview or import bundle",
"importing": "Importing…",
"invalid": "Paste a valid org bundle JSON document",
"label": "Org bundle JSON",
"placeholder": "Paste a secret-scrubbed org bundle",
"preview": "Preview import",
"previewReady": "Preview ready. Confirm to apply this bundle.",
"previewing": "Previewing…"
},
"title": "Org export / import"
},
"productivity": { "productivity": {
"averageDuration": "Average", "averageDuration": "Average",
"backfillAppliedLabel": "Applied", "backfillAppliedLabel": "Applied",
@@ -1719,10 +1763,6 @@ export default interface Resources {
"volumeHint": "volume, not outcome", "volumeHint": "volume, not outcome",
"volumeTitle": "Volume (proxy)" "volumeTitle": "Volume (proxy)"
}, },
"reviewArtifacts": {
"empty": "No review artifacts are available yet.",
"title": "Review artifacts"
},
"range": { "range": {
"custom": "Custom range", "custom": "Custom range",
"dialogLabel": "Select date range", "dialogLabel": "Select date range",
@@ -1730,6 +1770,10 @@ export default interface Resources {
"invalidRange": "Start date must be on or before end date", "invalidRange": "Start date must be on or before end date",
"to": "To" "to": "To"
}, },
"reviewArtifacts": {
"empty": "No review artifacts are available yet.",
"title": "Review artifacts"
},
"signals": { "signals": {
"bySeverity": "By severity", "bySeverity": "By severity",
"bySource": "By source", "bySource": "By source",
@@ -1769,8 +1813,8 @@ export default interface Resources {
"nodes": "Nodes", "nodes": "Nodes",
"overview": "Overview", "overview": "Overview",
"productivity": "Productivity", "productivity": "Productivity",
"reviewArtifacts": "Review artifacts",
"reliability": "Reliability", "reliability": "Reliability",
"reviewArtifacts": "Review artifacts",
"signals": "Signals", "signals": "Signals",
"system": "System", "system": "System",
"team": "Team", "team": "Team",
@@ -1991,6 +2035,7 @@ export default interface Resources {
"connectRemoteButton": "Connect to Remote Fusion", "connectRemoteButton": "Connect to Remote Fusion",
"couldNotStart": "Couldn't start local Fusion", "couldNotStart": "Couldn't start local Fusion",
"loading": "Loading Fusion…", "loading": "Loading Fusion…",
"migrationInProgress": "Database migration in progress — this can take a few minutes.",
"opening": "Opening…", "opening": "Opening…",
"retry": "Retry", "retry": "Retry",
"runLocalButton": "Run Fusion Locally", "runLocalButton": "Run Fusion Locally",
@@ -4539,6 +4584,7 @@ export default interface Resources {
"advancedSettings": "Advanced planning settings", "advancedSettings": "Advanced planning settings",
"agentClarification": "Allow agent clarification questions", "agentClarification": "Allow agent clarification questions",
"aiThinking": "AI is thinking...", "aiThinking": "AI is thinking...",
"answeredQuestions": "Answered questions",
"archiveSession": "Archive session", "archiveSession": "Archive session",
"backToSessions": "Back to sessions", "backToSessions": "Back to sessions",
"backToSummary": "Back to Summary", "backToSummary": "Back to Summary",
@@ -4557,11 +4603,6 @@ export default interface Resources {
"breakingDown": "Breaking down...", "breakingDown": "Breaking down...",
"collapse": "Collapse", "collapse": "Collapse",
"continue": "Continue", "continue": "Continue",
"nextQuestion": "Next question",
"answeredQuestions": "Answered questions",
"runningPlan": "Running plan",
"renameSession": "Rename session",
"editAnswer": "Edit answer for {{question}}",
"createSingleTask": "Create Single Task", "createSingleTask": "Create Single Task",
"createTasks": "Create Tasks", "createTasks": "Create Tasks",
"creating": "Creating...", "creating": "Creating...",
@@ -4576,6 +4617,7 @@ export default interface Resources {
"description": "Description", "description": "Description",
"dismiss": "Dismiss", "dismiss": "Dismiss",
"dragToReorder": "Drag to reorder", "dragToReorder": "Drag to reorder",
"editAnswer": "Edit answer for {{question}}",
"examplePlan1": "Build a user authentication system with login and signup", "examplePlan1": "Build a user authentication system with login and signup",
"examplePlan2": "Add dark mode support to the dashboard", "examplePlan2": "Add dark mode support to the dashboard",
"examplePlan3": "Create an API endpoint for exporting tasks as CSV", "examplePlan3": "Create an API endpoint for exporting tasks as CSV",
@@ -4607,13 +4649,13 @@ export default interface Resources {
"moveUp": "Move up", "moveUp": "Move up",
"newPlanningSession": "New planning session", "newPlanningSession": "New planning session",
"newSession": "New session", "newSession": "New session",
"nextQuestion": "Next question",
"noActiveQuestion": "No active question in session", "noActiveQuestion": "No active question in session",
"noPreviousSubtasks": "No previous subtasks available.", "noPreviousSubtasks": "No previous subtasks available.",
"noSavedSessions": "No saved sessions yet. Start one on the right to see it here.", "noSavedSessions": "No saved sessions yet. Start one on the right to see it here.",
"plain": "Plain", "plain": "Plain",
"planningComplete": "Planning Complete!", "planningComplete": "Planning Complete!",
"planningCompleteSubheading": "Review and refine your task before creating it.", "planningCompleteSubheading": "Review and refine your task before creating it.",
"planningDepth": "Planning depth",
"planningModel": "Planning Model", "planningModel": "Planning Model",
"planningModelBlurb": "Selects which model runs the planning interview and writes the final draft.", "planningModelBlurb": "Selects which model runs the planning interview and writes the final draft.",
"planningSessions": "Planning sessions", "planningSessions": "Planning sessions",
@@ -4633,8 +4675,10 @@ export default interface Resources {
"relativeTimeWeeks_one": "{{count}}w ago", "relativeTimeWeeks_one": "{{count}}w ago",
"relativeTimeWeeks_other": "{{count}}w ago", "relativeTimeWeeks_other": "{{count}}w ago",
"remove": "Remove", "remove": "Remove",
"renameSession": "Rename session",
"retryFailed": "Retry failed. Please try again.", "retryFailed": "Retry failed. Please try again.",
"retrying": "Retrying...", "retrying": "Retrying...",
"runningPlan": "Running plan",
"sessionFailed": "Session failed while contacting the AI.", "sessionFailed": "Session failed while contacting the AI.",
"sessionFailed2": "Session failed", "sessionFailed2": "Session failed",
"showArchived": "Show archived", "showArchived": "Show archived",
@@ -5143,6 +5187,19 @@ export default interface Resources {
}, },
"window": "Window: {{start}} → {{end}}" "window": "Window: {{start}} → {{end}}"
}, },
"report": {
"roadmapDuplicate": {
"title": "Already on the roadmap — add your data point?"
},
"roadmapMatch": {
"message": "This report matches a feature that is already planned.",
"title": "Already on the roadmap"
},
"targetDiscussion": "GitHub Discussion",
"targetInherit": "Use configured action target",
"targetIssue": "GitHub Issue",
"targetLabel": "Filing target"
},
"research": { "research": {
"actionFailed": "Action failed", "actionFailed": "Action failed",
"addCredentialsHint": "Add provider credentials in Authentication settings.", "addCredentialsHint": "Add provider credentials in Authentication settings.",
@@ -5807,7 +5864,27 @@ export default interface Resources {
"eGPnpmTest": "e.g. pnpm test", "eGPnpmTest": "e.g. pnpm test",
"testCommand": "Test Command" "testCommand": "Test Command"
}, },
"configVersions": {
"confirmMessage": "Restore this version? The rollback is recorded as a new version.",
"confirmRollback": "Roll back",
"confirmTitle": "Roll back configuration?",
"description": "Restore any recorded project configuration version.",
"empty": "No configuration versions yet.",
"loadError": "Unable to load configuration versions",
"loading": "Loading versions…",
"rollback": "Roll back",
"rollbackError": "Unable to roll back configuration",
"rollingBack": "Rolling back…",
"title": "Configuration versions"
},
"conflictModalTitle": "Resolve Settings Conflicts", "conflictModalTitle": "Resolve Settings Conflicts",
"database": {
"advanced": "Advanced database settings",
"embeddedConnectionCap": "Embedded PostgreSQL connection cap",
"embeddedConnectionCapError": "Enter a value between 32 and 2,000.",
"embeddedConnectionCapHelp": "Maximum server connections for Fusion's embedded PostgreSQL. Applies after restarting Fusion. Range: 32–2,000. Unset by default — Fusion picks 500, or 150 on Windows where each connection is a separate process and higher caps can crash backends. External PostgreSQL uses its provider's connection limit.",
"embeddedConnectionCapPlaceholder": "auto"
},
"experimental": { "experimental": {
"experimentalFeatures": "Experimental Features", "experimentalFeatures": "Experimental Features",
"experimentalFeaturesAreEarlyCapabilitiesThatAreNot": " Experimental features are early capabilities that are not yet fully stable. Enable them to test new functionality, but be aware they may change or be removed. Default: disabled for every feature flag below. ", "experimentalFeaturesAreEarlyCapabilitiesThatAreNot": " Experimental features are early capabilities that are not yet fully stable. Enable them to test new functionality, but be aware they may change or be removed. Default: disabled for every feature flag below. ",
@@ -5850,8 +5927,6 @@ export default interface Resources {
"chatHistory": "Chat history", "chatHistory": "Chat history",
"chatRooms": "Chat Rooms", "chatRooms": "Chat Rooms",
"completionDocumentationAutomation": "Completion Documentation Automation", "completionDocumentationAutomation": "Completion Documentation Automation",
"reviewArtifacts": "Review Artifacts",
"reviewArtifactsHint": "Controls whether eligible future tasks generate review deliverables. User-facing limits generation to user-facing work; on enables it for all eligible tasks. Individual PROMPT.md headers can override this. Default: off.",
"controlsHowFutureTaskSpecsHandleReleaseNote": " Controls how future task specs handle release-note artifacts at completion. Use changeset mode for repositories that follow ", "controlsHowFutureTaskSpecsHandleReleaseNote": " Controls how future task specs handle release-note artifacts at completion. Use changeset mode for repositories that follow ",
"controlsWhetherNewlyCreatedTasksHaveGitHubIssue": " Controls whether newly created tasks have GitHub issue tracking enabled by default. Individual tasks can still override this from the task detail modal. ", "controlsWhetherNewlyCreatedTasksHaveGitHubIssue": " Controls whether newly created tasks have GitHub issue tracking enabled by default. Individual tasks can still override this from the task detail modal. ",
"defaultRepoUsedWhenCreatingGitHubIssuesFor": "Default repo used when creating GitHub issues for tracked tasks. Falls back to the global default if blank.", "defaultRepoUsedWhenCreatingGitHubIssuesFor": "Default repo used when creating GitHub issues for tracked tasks. Falls back to the global default if blank.",
@@ -5861,6 +5936,11 @@ export default interface Resources {
"disabledBuiltInWorkflowsAreHiddenFromWorkflow": "Disabled built-in workflows are hidden from workflow pickers. Existing tasks that already use one continue to resolve.", "disabledBuiltInWorkflowsAreHiddenFromWorkflow": "Disabled built-in workflows are hidden from workflow pickers. Existing tasks that already use one continue to resolve.",
"disabledFusionWorkflowsAreHiddenFromWorkflow": "Disabled Fusion workflows are hidden from workflow pickers. Existing tasks that already use one continue to resolve. Default: all built-in workflows enabled (unset).", "disabledFusionWorkflowsAreHiddenFromWorkflow": "Disabled Fusion workflows are hidden from workflow pickers. Existing tasks that already use one continue to resolve. Default: all built-in workflows enabled (unset).",
"enableSummarizationInProjectModelsToConfigureThatModel": " Enable summarization in Project Models to configure that model.", "enableSummarizationInProjectModelsToConfigureThatModel": " Enable summarization in Project Models to configure that model.",
"ephemeralAgentTaskCreationPolicy": "Ephemeral agent follow-up tasks",
"ephemeralAgentTaskCreationPolicyAllow": "Allow",
"ephemeralAgentTaskCreationPolicyDeny": "Deny",
"ephemeralAgentTaskCreationPolicyHint": "No default — unset policy falls back to Allow. Upon validation sends a proposal to your mailbox for one-click approval; Deny rejects follow-up task creation.",
"ephemeralAgentTaskCreationPolicyUponValidation": "Upon validation",
"fN": "FN", "fN": "FN",
"followDashboardLanguage": "Follow dashboard language", "followDashboardLanguage": "Follow dashboard language",
"general": "General", "general": "General",
@@ -5887,12 +5967,41 @@ export default interface Resources {
"quickChatLauncherHint": "Choose whether Quick Chat opens from the draggable floating button, a footer button beside Terminal, or stays hidden. Default: off (hidden).", "quickChatLauncherHint": "Choose whether Quick Chat opens from the draggable floating button, a footer button beside Terminal, or stays hidden. Default: off (hidden).",
"recentVerbatimRoomMessages": "Recent verbatim room messages", "recentVerbatimRoomMessages": "Recent verbatim room messages",
"removeNavItem": "Remove {{item}}", "removeNavItem": "Remove {{item}}",
"reportDiscussionCategory": "Discussion category",
"reportDiscussionCategoryEmpty": "No Discussion categories are available for the configured repository.",
"reportDiscussionCategoryHelp": "Required when filing to GitHub Discussions. Categories are loaded from the configured repository. No default — unset until a category is selected.",
"reportDiscussionCategorySelect": "Select a Discussion category",
"reportDiscussionCategoryUnavailable": "Discussion categories are unavailable for the configured repository.",
"reportModeByActionHelp": "Optional per-action override of the project report mode for bug, feedback, idea, or help. No default — unset actions inherit reportMode.",
"reportModeHelp": "How in-app bug/feedback/idea/help reports are filed. Default: draft-review (operator reviews a draft before filing).",
"reportRoadmapDedup": "Check roadmap before filing reports",
"reportRoadmapDedupHelp": "When enabled, matching roadmap features are shown inline instead of filing another GitHub Issue or Discussion. Default: off.",
"reportRoadmapDedupeEnabled": "Deduplicate reports against public roadmap",
"reportRoadmapDedupeEnabledHelp": "Match open, labeled roadmap issues before filing. Default: on.",
"reportRoadmapLabel": "Public roadmap label",
"reportRoadmapLabelHelp": "Open GitHub Issues with this label are considered roadmap items. Default: roadmap.",
"reportRoadmapRepo": "Public roadmap repository (optional)",
"reportRoadmapRepoHelp": "GitHub owner/repository containing public roadmap issues. When unset, uses the tracking repository. No default — unset.",
"reportTarget": "Default report target",
"reportTargetByActionHelp": "Optional per-action override of the default report target for bug, feedback, idea, or help. No default — unset actions inherit reportTarget / action defaults.",
"reportTargetDiscussion": "GitHub Discussion",
"reportTargetHelp": "Optional Issue or Discussion target. When unset, Feedback and Help use Discussions while Bug and Idea use Issues. No default — unset.",
"reportTargetInherit": "Preserve action default",
"reportTargetIssue": "GitHub Issue",
"reportTargetOverride": {
"bug": "Bug target override",
"feedback": "Feedback target override",
"help": "Help target override",
"idea": "Idea target override"
},
"requireChangelogUpdateExistingChangelog": "Require changelog update (existing changelog)", "requireChangelogUpdateExistingChangelog": "Require changelog update (existing changelog)",
"requireChangesetChangesetMd": "Require changeset (.changeset/*.md)", "requireChangesetChangesetMd": "Require changeset (.changeset/*.md)",
"restartFailed": "Restart could not be scheduled. Try restarting Fusion manually.", "restartFailed": "Restart could not be scheduled. Try restarting Fusion manually.",
"restartNow": "Restart Fusion", "restartNow": "Restart Fusion",
"restartUnavailable": "Needs a supervising parent — restart Fusion manually without --no-supervise.", "restartUnavailable": "Needs a supervising parent — restart Fusion manually without --no-supervise.",
"restarting": "Restarting… Your connection will close shortly.", "restarting": "Restarting… Your connection will close shortly.",
"reviewArtifacts": "Review Artifacts",
"reviewArtifactsHint": "Controls whether eligible future tasks generate review deliverables. User-facing limits generation to user-facing work; on enables it for all eligible tasks. Individual PROMPT.md headers can override this. Default: off.",
"roomCompactionFetchLimit": "Room compaction fetch limit", "roomCompactionFetchLimit": "Room compaction fetch limit",
"roomSummaryMaxCharacters": "Room summary max characters", "roomSummaryMaxCharacters": "Room summary max characters",
"searchTheTrackingRepoForLikelyDuplicatesBefore": " Search the tracking repo for likely duplicates before opening a new issue ", "searchTheTrackingRepoForLikelyDuplicatesBefore": " Search the tracking repo for likely duplicates before opening a new issue ",
@@ -5930,12 +6039,16 @@ export default interface Resources {
"autoReloadDashboardOnVersionChange": " Auto-reload dashboard on version change ", "autoReloadDashboardOnVersionChange": " Auto-reload dashboard on version change ",
"cLIBinaryOnPATH": " CLI binary on PATH ", "cLIBinaryOnPATH": " CLI binary on PATH ",
"cLIBySpawning": " CLI by spawning", "cLIBySpawning": " CLI by spawning",
"channelBeta": "Beta — early builds from main",
"channelStable": "Stable (recommended)",
"checkForThe": " Check for the ", "checkForThe": " Check for the ",
"checkForUpdatesAutomatically": " Check for updates automatically ", "checkForUpdatesAutomatically": " Check for updates automatically ",
"controlsHowOftenTheDashboardReFetchesThe": " Controls how often the dashboard re-fetches the npm registry. Use the version + refresh control in the header to trigger an immediate check at any time. Default: daily. ", "controlsHowOftenTheDashboardReFetchesThe": " Controls how often the dashboard re-fetches the npm registry. Use the version + refresh control in the header to trigger an immediate check at any time. Default: daily. ",
"dailyRecommended": "Daily (recommended)", "dailyRecommended": "Daily (recommended)",
"disableThisIfYourLocalDevProcessIs": ". Disable this if your local dev process is the source of truth and you don&apos;t want any outdated globally-installed binary executed during the probe. Default: enabled. ", "disableThisIfYourLocalDevProcessIs": ". Disable this if your local dev process is the source of truth and you don&apos;t want any outdated globally-installed binary executed during the probe. Default: enabled. ",
"dismissModalsByClickingOutsideHint": " When enabled, clicking or tapping a modal backdrop closes the modal. Default: disabled, to prevent accidental dismissal. ", "dismissModalsByClickingOutsideHint": " When enabled, clicking or tapping a modal backdrop closes the modal. Default: disabled, to prevent accidental dismissal. ",
"enableProactiveTaskChat": " Enable proactive task-chat updates ",
"enableProactiveTaskChatHint": " When enabled, Task chat reports step progress, failures, reviews, and rollbacks in real time. Default: disabled. ",
"frequency": "Frequency", "frequency": "Frequency",
"general": "General", "general": "General",
"gitLabApiBaseUrlHint": "Blank derives <instance>/api/v4. Override only for self-managed GitLab API gateways that use a different absolute http:// or https:// URL. No default — unset.", "gitLabApiBaseUrlHint": "Blank derives <instance>/api/v4. Override only for self-managed GitLab API gateways that use a different absolute http:// or https:// URL. No default — unset.",
@@ -5949,6 +6062,14 @@ export default interface Resources {
"onStartupOncePerServerLaunch": "On startup — once per server launch", "onStartupOncePerServerLaunch": "On startup — once per server launch",
"ownerRepo": "owner/repo", "ownerRepo": "owner/repo",
"projectsInheritThisValueWhenTheyDoNot": "Projects inherit this value when they do not set a project default tracking repo. No default — unset.", "projectsInheritThisValueWhenTheyDoNot": "Projects inherit this value when they do not set a project default tracking repo. No default — unset.",
"releaseChannel": "Release channel",
"releaseChannelHelp": "Stable follows official releases. Beta follows pre-releases cut from main and also picks up each stable release once it overtakes the beta. Switching back to Stable never downgrades. Default: stable.",
"reportRoadmapDedupeEnabled": "Global public-roadmap deduplication",
"reportRoadmapDedupeEnabledHelp": "Fallback for projects that do not set a public-roadmap deduplication preference. No default — unset (projects default to enabled).",
"reportRoadmapLabel": "Global public roadmap label",
"reportRoadmapLabelHelp": "Fallback label for open public-roadmap issues when a project does not set one. No default — unset (projects default to roadmap).",
"reportRoadmapRepo": "Global public roadmap repository (optional)",
"reportRoadmapRepoHelp": "Fallback GitHub owner/repository for public-roadmap issues. When unset, uses the tracking repository. No default — unset.",
"rowsAndDoesNotAffectAssistantTextOr": " rows and does not affect assistant text or tool rows. Default: disabled for both permanent and ephemeral agents. ", "rowsAndDoesNotAffectAssistantTextOr": " rows and does not affect assistant text or tool rows. Default: disabled for both permanent and ephemeral agents. ",
"saveAIThinkingForEphemeralTaskWorkerAgents": " Save AI thinking for ephemeral / task-worker agents ", "saveAIThinkingForEphemeralTaskWorkerAgents": " Save AI thinking for ephemeral / task-worker agents ",
"saveAIThinkingForPermanentAgents": " Save AI thinking for permanent agents ", "saveAIThinkingForPermanentAgents": " Save AI thinking for permanent agents ",
@@ -6290,6 +6411,7 @@ export default interface Resources {
"global": "Global setting", "global": "Global setting",
"project": "Project setting" "project": "Project setting"
}, },
"configVersions": "Configuration Versions",
"globalMcp": "MCP Servers · Global", "globalMcp": "MCP Servers · Global",
"mcp": "MCP Servers · Project", "mcp": "MCP Servers · Project",
"prompts": "Prompts", "prompts": "Prompts",
@@ -7833,7 +7955,12 @@ export default interface Resources {
"createdBy": "Created by", "createdBy": "Created by",
"createdToUndo": "Created to undo", "createdToUndo": "Created to undo",
"createdVia": "Created via", "createdVia": "Created via",
"creatingAgent": "Creating agent",
"importedFrom": "Imported from",
"nearDuplicateOf": "Flagged near-duplicate of",
"parentTask": "Parent task",
"parentTaskOf": "of", "parentTaskOf": "of",
"title": "Provenance",
"undoTask": "Undo task" "undoTask": "Undo task"
}, },
"recoveryState": "Recovery state", "recoveryState": "Recovery state",
@@ -7996,7 +8123,7 @@ export default interface Resources {
}, },
"tokenTotalsAria": "Task token totals", "tokenTotalsAria": "Task token totals",
"tokenUsage": "Token Usage", "tokenUsage": "Token Usage",
"totalExecutionTime": "Total execution time", "totalExecutionTime": "Total active time",
"totalTokens": "Total", "totalTokens": "Total",
"updateFailed": "Failed to update {{id}}: {{error}}", "updateFailed": "Failed to update {{id}}: {{error}}",
"updateSuccess": "Updated {{id}}", "updateSuccess": "Updated {{id}}",
@@ -8455,6 +8582,8 @@ export default interface Resources {
"failedToStartTerminal": "Failed to start terminal: {{error}}", "failedToStartTerminal": "Failed to start terminal: {{error}}",
"increaseFontSize": "Increase terminal font size", "increaseFontSize": "Increase terminal font size",
"initializeError": "Terminal UI failed to initialize: {{error}}", "initializeError": "Terminal UI failed to initialize: {{error}}",
"manualStartError": "Failed to start terminal: {{message}}",
"manualStartHint": "The terminal is ready — start a session to begin.",
"newSession": "New Session", "newSession": "New Session",
"newTerminal": "New terminal", "newTerminal": "New terminal",
"preferenceCursorBlink": "Blink cursor", "preferenceCursorBlink": "Blink cursor",
@@ -8471,6 +8600,7 @@ export default interface Resources {
"rendererReopenNote": "Reopen the terminal to apply renderer changes.", "rendererReopenNote": "Reopen the terminal to apply renderer changes.",
"resetPreferences": "Reset to defaults", "resetPreferences": "Reset to defaults",
"shortcuts": "Shortcuts", "shortcuts": "Shortcuts",
"startTerminal": "Start terminal",
"startingTerminal": "Starting terminal...", "startingTerminal": "Starting terminal...",
"statusConnected": "Connected", "statusConnected": "Connected",
"statusConnecting": "Connecting...", "statusConnecting": "Connecting...",