fix: copy and content improvements in goose2 (#8886)

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
Clay Delk
2026-04-29 21:48:57 -04:00
committed by GitHub
parent 000d7c58de
commit 2403a39e1a
43 changed files with 289 additions and 145 deletions
@@ -299,7 +299,11 @@ export function AgentsView() {
>
<AlertDialogContent className="max-w-sm">
<AlertDialogHeader>
<AlertDialogTitle>{t("view.deleteTitle")}</AlertDialogTitle>
<AlertDialogTitle>
{t("view.deleteTitle", {
name: deletingPersona?.displayName ?? "",
})}
</AlertDialogTitle>
<AlertDialogDescription>
{t("view.deleteDescription", {
name: deletingPersona?.displayName ?? "",
@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import {
getChatInputAgentLabel,
getChatInputPlaceholder,
} from "./chatInputPlaceholder";
const t = (key: string, options?: { agent: string }) =>
options?.agent ? `${key}:${options.agent}` : key;
describe("getChatInputAgentLabel", () => {
it("uses the active persona display name when present", () => {
expect(getChatInputAgentLabel("Reviewer", "Goose")).toBe("Reviewer");
});
it("falls back to the provider display name", () => {
expect(getChatInputAgentLabel(undefined, "Goose")).toBe("Goose");
});
it("preserves explicit persona names with the default suffix", () => {
expect(getChatInputAgentLabel("Ops (Default)", "Goose (Default)")).toBe(
"Ops (Default)",
);
});
it("removes the default suffix from provider fallback labels", () => {
expect(getChatInputAgentLabel(undefined, "Goose (Default)")).toBe("Goose");
});
});
describe("getChatInputPlaceholder", () => {
it("uses the agent label in the default placeholder", () => {
expect(getChatInputPlaceholder(t, "Goose", false, false)).toBe(
"input.placeholder:Goose",
);
});
it("uses voice status placeholders while recording or transcribing", () => {
expect(getChatInputPlaceholder(t, "Goose", true, false)).toBe(
"toolbar.voiceInputRecording",
);
expect(getChatInputPlaceholder(t, "Goose", false, true)).toBe(
"toolbar.voiceInputTranscribing",
);
});
});
@@ -1,3 +1,18 @@
const DEFAULT_LABEL_SUFFIX = " (Default)";
export function getChatInputAgentLabel(
personaDisplayName: string | undefined,
providerDisplayName: string,
): string {
if (personaDisplayName) {
return personaDisplayName;
}
return providerDisplayName.endsWith(DEFAULT_LABEL_SUFFIX)
? providerDisplayName.slice(0, -DEFAULT_LABEL_SUFFIX.length)
: providerDisplayName;
}
export function getChatInputPlaceholder(
t: (key: string, options?: { agent: string }) => string,
agent: string,
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import type { Message } from "@/shared/types/messages";
import { findExistingDraft } from "./newChat";
import type { ChatSession } from "../stores/chatSessionStore";
@@ -8,7 +9,7 @@ function makeSession(
): ChatSession {
return {
id,
title: "New Chat",
title: "New chat",
createdAt: "2026-04-01T00:00:00.000Z",
updatedAt: "2026-04-01T00:00:00.000Z",
messageCount: 0,
@@ -30,7 +31,7 @@ describe("findExistingDraft", () => {
draftsBySession: { "alpha-draft": "alpha draft" },
messagesBySession: {},
request: {
title: "New Chat",
title: "New chat",
projectId: "alpha",
},
}),
@@ -111,7 +112,12 @@ describe("findExistingDraft", () => {
draftsBySession: {},
messagesBySession: {
"alpha-session": [
{ id: "msg-1", role: "user", content: "hello" } as any,
{
id: "msg-1",
role: "user",
created: 1,
content: [{ type: "text", text: "hello" }],
} satisfies Message,
],
},
request: {
@@ -4,6 +4,7 @@ import {
getDisplaySessionTitle,
getEditableSessionTitle,
getSessionTitleFromDraft,
isDefaultChatTitle,
isSessionTitleUnchanged,
} from "./sessionTitle";
@@ -17,6 +18,11 @@ describe("sessionTitle", () => {
);
});
it("treats the ACP title-case default title as the default title", () => {
expect(isDefaultChatTitle("New Chat")).toBe(true);
expect(getDisplaySessionTitle("New Chat", "Nuevo chat")).toBe("Nuevo chat");
});
it("treats the localized default title as unchanged while the sentinel is still internal", () => {
expect(
isSessionTitleUnchanged("Nuevo chat", DEFAULT_CHAT_TITLE, "Nuevo chat"),
@@ -1,9 +1,10 @@
import type { ChatAttachmentDraft } from "@/shared/types/messages";
export const DEFAULT_CHAT_TITLE = "New Chat";
export const DEFAULT_CHAT_TITLE = "New chat";
const ACP_DEFAULT_CHAT_TITLE = "New Chat";
export function isDefaultChatTitle(title: string): boolean {
return title === DEFAULT_CHAT_TITLE;
return title === DEFAULT_CHAT_TITLE || title === ACP_DEFAULT_CHAT_TITLE;
}
function attachmentKindLabel(kind: ChatAttachmentDraft["kind"], count: number) {
@@ -17,6 +18,14 @@ function attachmentKindLabel(kind: ChatAttachmentDraft["kind"], count: number) {
}
}
// The goose ACP backend uses "New Chat" (title case) as its default — normalize to ours.
export function normalizeAcpTitle(
title: string | null | undefined,
): string | undefined {
if (!title) return undefined;
return title === ACP_DEFAULT_CHAT_TITLE ? DEFAULT_CHAT_TITLE : title;
}
export function getSessionTitleFromDraft(
text: string,
attachments?: ChatAttachmentDraft[],
@@ -5,7 +5,10 @@ import {
type AcpSessionInfo,
} from "@/shared/api/acp";
import type { Session } from "@/shared/types/chat";
import { DEFAULT_CHAT_TITLE } from "@/features/chat/lib/sessionTitle";
import {
DEFAULT_CHAT_TITLE,
normalizeAcpTitle,
} from "@/features/chat/lib/sessionTitle";
import {
archiveSession as acpArchiveSession,
unarchiveSession as acpUnarchiveSession,
@@ -97,7 +100,7 @@ function acpSessionToChatSession(session: AcpSessionInfo): ChatSession {
return {
id: session.sessionId,
acpSessionId: session.sessionId,
title: session.title ?? "Untitled",
title: normalizeAcpTitle(session.title) ?? "Untitled",
projectId: session.projectId ?? undefined,
providerId: session.providerId ?? undefined,
personaId: session.personaId ?? undefined,
+8 -2
View File
@@ -5,7 +5,10 @@ import {
attachmentSnapshotsMatch,
skillDraftSnapshotsMatch,
} from "../lib/chatInputSnapshots";
import { getChatInputPlaceholder } from "../lib/chatInputPlaceholder";
import {
getChatInputAgentLabel,
getChatInputPlaceholder,
} from "../lib/chatInputPlaceholder";
import { cn } from "@/shared/lib/cn";
import { Badge } from "@/shared/ui/badge";
import { Popover, PopoverAnchor } from "@/shared/ui/popover";
@@ -330,7 +333,10 @@ export function ChatInput({
const providerDisplayName =
providers.find((provider) => provider.id === selectedProvider)?.label ??
formatProviderLabel(selectedProvider);
const agentDisplayName = activePersona?.displayName ?? providerDisplayName;
const agentDisplayName = getChatInputAgentLabel(
activePersona?.displayName,
providerDisplayName,
);
const resolvedCurrentModel = useMemo(() => {
if (currentModel) {
return currentModel;
@@ -94,9 +94,7 @@ describe("ChatInput", () => {
it("renders with default placeholder", () => {
render(<ChatInput onSend={vi.fn()} />);
expect(
screen.getByPlaceholderText(
"Message Goose, @ to mention agents or skills",
),
screen.getByPlaceholderText("Chat with Goose or @ mention an agent"),
).toBeInTheDocument();
});
@@ -39,9 +39,7 @@ describe("FilesList", () => {
render(<FilesList />);
expect(
screen.getByText(
"Project files are unavailable until a project with working directories is assigned.",
),
screen.getByText("Files will show here after you assign a project."),
).toBeInTheDocument();
});
@@ -183,9 +183,7 @@ describe("HomeScreen", () => {
it("renders the chat input placeholder with default agent name when no persona selected", () => {
renderHome();
expect(
screen.getByPlaceholderText(
"Message Goose, @ to mention agents or skills",
),
screen.getByPlaceholderText("Chat with Goose or @ mention an agent"),
).toBeInTheDocument();
});
@@ -279,7 +279,11 @@ export function ProjectsView({ onStartChat }: ProjectsViewProps) {
>
<AlertDialogContent className="max-w-sm">
<AlertDialogHeader>
<AlertDialogTitle>{t("view.deleteTitle")}</AlertDialogTitle>
<AlertDialogTitle>
{t("view.deleteTitle", {
name: deletingProject?.name ?? "",
})}
</AlertDialogTitle>
<AlertDialogDescription>
{t("view.deleteDescription", {
name: deletingProject?.name ?? "",
@@ -155,13 +155,13 @@ describe("CreateProjectDialog", () => {
/>,
);
expect(screen.getByText("Edit Project")).toBeInTheDocument();
expect(screen.getByText("Edit project")).toBeInTheDocument();
});
it("shows New Project title without editingProject", () => {
render(<CreateProjectDialog {...defaultProps} isOpen={true} />);
expect(screen.getByText("New Project")).toBeInTheDocument();
expect(screen.getByText("New project")).toBeInTheDocument();
});
it("populates the prompt editor with working dirs and prompt text", () => {
@@ -60,9 +60,6 @@ export function AppearanceSettings() {
<h3 className="text-lg font-semibold font-display tracking-tight">
{t("appearance.title")}
</h3>
<p className="mt-1 text-sm text-muted-foreground">
{t("appearance.description")}
</p>
<Separator className="my-4" />
@@ -124,21 +124,19 @@ export function DoctorCheckRow({ check, onFixed }: DoctorCheckRowProps) {
<AlertDialogContent className="max-w-sm">
<AlertDialogHeader>
<AlertDialogTitle>{t("settings:doctor.runFix")}</AlertDialogTitle>
<AlertDialogDescription className="break-all font-mono">
{check.fixCommand}
<AlertDialogDescription>
{t("settings:doctor.runFixDescription")}
</AlertDialogDescription>
<code className="block break-all rounded bg-muted px-3 py-2 font-mono text-xs">
{check.fixCommand}
</code>
</AlertDialogHeader>
{fixError && <p className="text-xs text-destructive">{fixError}</p>}
<AlertDialogFooter>
<AlertDialogCancel disabled={fixing}>
{t("common:actions.cancel")}
</AlertDialogCancel>
<Button
variant="outline"
size="sm"
disabled={fixing}
onClick={confirmFix}
>
<Button disabled={fixing} onClick={confirmFix}>
{fixing && <Loader2 className="h-3 w-3 animate-spin" />}
{fixing
? t("common:actions.running")
@@ -99,9 +99,6 @@ export function DoctorSettings() {
<h3 className="text-lg font-semibold font-display tracking-tight">
{t("doctor.title")}
</h3>
<p className="mt-1 text-sm text-muted-foreground">
{t("doctor.description")}
</p>
</div>
<div className="flex flex-shrink-0 items-center gap-2">
@@ -331,6 +331,7 @@ export function ModelProviderRow({
const fieldSetupDescription = getFieldSetupDescription(
provider.setupMethod,
t,
provider.fields,
);
if (loadingConfig && hasFields) {
@@ -117,9 +117,6 @@ export function ProvidersSettings() {
<h3 className="text-lg font-semibold font-display tracking-tight">
{t("providers.title")}
</h3>
<p className="mt-1 text-sm text-muted-foreground">
{t("providers.description")}
</p>
<Separator className="my-4" />
@@ -377,7 +377,11 @@ export function SettingsModal({
>
<AlertDialogContent className="max-w-sm">
<AlertDialogHeader>
<AlertDialogTitle>{t("deleteProject.title")}</AlertDialogTitle>
<AlertDialogTitle>
{t("deleteProject.title", {
name: deletingProject?.name ?? "",
})}
</AlertDialogTitle>
<AlertDialogDescription>
{t("deleteProject.description", {
name: deletingProject?.name ?? "",
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import { getFieldSetupDescription } from "../modelProviderHelpers";
const t = (key: string) => key;
describe("getFieldSetupDescription", () => {
it("uses single API key copy for config fields with one required secret API key", () => {
expect(
getFieldSetupDescription("config_fields", t, [
{
key: "OPENAI_API_KEY",
label: "API Key",
secret: true,
required: true,
placeholder: "Paste your API key",
},
]),
).toBe("providers.models.setup.fieldDescription.singleApiKey");
});
it("uses generic config fields copy for config fields without an API key field", () => {
expect(
getFieldSetupDescription("config_fields", t, [
{
key: "OLLAMA_HOST",
label: "Host",
secret: false,
required: true,
placeholder: "localhost or http://localhost:11434",
},
]),
).toBe("providers.models.setup.fieldDescription.configFields");
});
});
@@ -103,10 +103,24 @@ export function getNativeConnectDescription(
}
}
function hasSingleApiKeyField(fields?: ProviderField[]): boolean {
if (fields?.length !== 1) {
return false;
}
const [field] = fields;
return field.secret && field.required && /(?:^|_)API_KEY$/.test(field.key);
}
export function getFieldSetupDescription(
setupMethod: ProviderSetupMethod,
t: (key: string) => string,
fields?: ProviderField[],
): string | null {
if (setupMethod === "config_fields" && hasSingleApiKeyField(fields)) {
return t("providers.models.setup.fieldDescription.singleApiKey");
}
switch (setupMethod) {
case "single_api_key":
return t("providers.models.setup.fieldDescription.singleApiKey");
@@ -155,7 +155,7 @@ describe("SidebarChatRow", () => {
await user.dblClick(screen.getByTitle("Double-click to rename"));
const input = screen.getByRole("textbox");
expect(input).toHaveValue("New Chat");
expect(input).toHaveValue("New chat");
await user.tab();
@@ -57,7 +57,9 @@ export function SkillsDialogs({
>
<AlertDialogContent className="max-w-sm">
<AlertDialogHeader>
<AlertDialogTitle>{t("view.deleteTitle")}</AlertDialogTitle>
<AlertDialogTitle>
{t("view.deleteTitle", { name: deletingSkill?.name ?? "" })}
</AlertDialogTitle>
<AlertDialogDescription>
{t("view.deleteDescription", {
name: deletingSkill?.name ?? "",
@@ -43,7 +43,7 @@ describe("CreateSkillDialog", () => {
it('shows "New Skill" title in create mode', () => {
render(<CreateSkillDialog {...defaultProps} />);
expect(screen.getByText("New Skill")).toBeInTheDocument();
expect(screen.getByText("New skill")).toBeInTheDocument();
});
it('shows "Edit Skill" title when editingSkill is provided', () => {
@@ -59,7 +59,7 @@ describe("CreateSkillDialog", () => {
}}
/>,
);
expect(screen.getByText("Edit Skill")).toBeInTheDocument();
expect(screen.getByText("Edit skill")).toBeInTheDocument();
});
});
@@ -115,7 +115,7 @@ describe("SkillsView", () => {
render(<SkillsView />);
expect(screen.getByText("Skills")).toBeInTheDocument();
expect(
screen.getByText(/Skills are reusable instructions/),
screen.getByText(/Use skills to add specific instructions/),
).toBeInTheDocument();
});
@@ -356,7 +356,9 @@ describe("SkillsView", () => {
await user.keyboard("{Enter}");
await user.click(screen.getByRole("menuitem", { name: "Delete" }));
expect(screen.getByText("Delete skill?")).toBeInTheDocument();
expect(
screen.getByText('Delete "code-review" permanently?'),
).toBeInTheDocument();
const deleteButtons = screen.getAllByRole("button", { name: "Delete" });
await user.click(deleteButtons[deleteButtons.length - 1]);
@@ -68,8 +68,8 @@
"view": {
"copyName": "{{name}} (Copy)",
"deleteFailed": "Failed to delete agent.",
"deleteDescription": "Are you sure you want to delete \"{{name}}\"? This cannot be undone.",
"deleteTitle": "Delete agent?",
"deleteDescription": "This agent and its configuration will be permanently removed.",
"deleteTitle": "Delete \"{{name}}\" permanently?",
"deleted": "\"{{name}}\" deleted.",
"description": "Custom agent configurations for specific workflows",
"emptyAgentsDescription": "Create an agent to get started.",
+11 -10
View File
@@ -25,7 +25,7 @@
"branchName": "Branch name",
"branchNamePlaceholder": "feature/my-branch",
"branchSuccess": "Created and switched to {{branch}}.",
"branchTitle": "New Branch",
"branchTitle": "New branch",
"branchToOpen": "Branch to open",
"cancel": "Cancel",
"createBranch": "Create branch",
@@ -40,17 +40,17 @@
"worktreeNamePlaceholder": "my-worktree",
"worktreePath": "Folder: {{path}}",
"worktreeSuccess": "Created worktree {{worktree}}.",
"worktreeTitle": "New Worktree"
"worktreeTitle": "New worktree"
},
"empty": {
"folderNotSet": "Folder not set",
"noChanges": "No uncommitted changes",
"noProjectAssigned": "No project assigned.",
"noProjectAssigned": "No project assigned",
"noExtensions": "No extensions enabled",
"noMatchingExtensions": "No matching extensions"
},
"errors": {
"gitRead": "Unable to read git status."
"gitRead": "Couldn't read git status."
},
"picker": {
"selectContext": "Select worktree or branch",
@@ -58,8 +58,9 @@
"allBranches": "All branches",
"checkedOutBranch": "{{branch}}",
"currentBranch": "Current branch",
"dirtyTitle": "Uncommitted Changes",
"dirtyDescription": "You have {{count}} uncommitted change(s). How would you like to proceed?",
"dirtyTitle": "Uncommitted changes",
"dirtyDescription_one": "You have {{count}} uncommitted change. How would you like to proceed?",
"dirtyDescription_other": "You have {{count}} uncommitted changes. How would you like to proceed?",
"stashAndSwitch": "Stash & Switch",
"carryChanges": "Carry to Branch",
"cancel": "Cancel",
@@ -96,19 +97,19 @@
}
},
"files": {
"empty": "Project files are unavailable until a project with working directories is assigned.",
"empty": "Files will show here after you assign a project.",
"folderEmpty": "Folder is empty",
"loadError": "Unable to load folder contents.",
"loadError": "Couldn't load this folder. Try again.",
"loading": "Loading files...",
"openFolder": "Open folder: {{path}}",
"rootLoadError": "Unable to load this folder."
"rootLoadError": "Couldn't load this folder. Try again."
},
"image": {
"view": "View {{label}}"
},
"input": {
"ariaLabel": "Chat message input",
"placeholder": "Message {{agent}}, @ to mention agents or skills"
"placeholder": "Chat with {{agent}} or @ mention an agent"
},
"loading": {
"compacting": "Compacting conversation...",
@@ -24,7 +24,7 @@
"run": "Run",
"running": "Running",
"save": "Save",
"saveChanges": "Save Changes",
"saveChanges": "Save changes",
"search": "Search",
"select": "Select",
"send": "Send",
@@ -42,7 +42,7 @@
},
"environmentVariables": {
"copyLabel": "Copy environment variable",
"title": "Environment Variables",
"title": "Environment variables",
"toggleVisibility": "Toggle value visibility"
},
"linkSafety": {
@@ -78,7 +78,7 @@
},
"labels": {
"assistant": "Assistant",
"copyPath": "Copy Path",
"copyPath": "Copy path",
"revealInFileManager_mac": "Reveal in Finder",
"revealInFileManager_windows": "Reveal in Explorer",
"revealInFileManager_linux": "Reveal in File Manager",
@@ -91,7 +91,7 @@
"required": "Required"
},
"session": {
"defaultTitle": "New Chat"
"defaultTitle": "New chat"
},
"usage": {
"cache": "Cache",
@@ -1,14 +1,14 @@
{
"dialog": {
"addDirectory": "Add directory",
"addDirectoryDialogTitle": "Select Directory",
"addDirectoryDialogTitle": "Select directory",
"color": "Color",
"colorAria": "Color {{color}}",
"createProject": "Create Project",
"createProject": "Create project",
"creating": "Creating...",
"customIcon": "Custom icon",
"customIconDialogTitle": "Select Icon",
"editTitle": "Edit Project",
"editTitle": "Edit project",
"icon": "Icon",
"iconAria": "Icon {{icon}}",
"iconCandidateTitle": "{{sourceDir}}: {{label}}",
@@ -37,26 +37,26 @@
"instructionsPlaceholder": "System prompt or context for agents working in this project...",
"name": "Name",
"namePlaceholder": "My Project",
"newTitle": "New Project",
"newTitle": "New project",
"noneUseDefault": "None (use default)",
"provider": "Provider",
"saving": "Saving...",
"scanningIcons": "Scanning...",
"uploadIcon": "Upload",
"useWorktrees": "Use git worktrees for branch isolation"
"useWorktrees": "Use Git worktrees for branch isolation"
},
"view": {
"deleteDescription": "Are you sure you want to delete \"{{name}}\"? This cannot be undone.",
"deleteTitle": "Delete project?",
"deleteDescription": "This project and all its data will be permanently removed.",
"deleteTitle": "Delete \"{{name}}\" permanently?",
"description": "Organize your work into focused project contexts",
"emptyDescription": "Create a project to organize your work.",
"emptyTitle": "No projects yet",
"newProject": "New Project",
"newProject": "New project",
"noMatchesDescription": "Try a different search term.",
"noMatchesTitle": "No matching projects",
"optionsAria": "Options for {{name}}",
"searchPlaceholder": "Search projects by name or description...",
"startChat": "Start Chat",
"startChat": "Start chat",
"title": "Projects"
}
}
@@ -9,7 +9,7 @@
},
"history": {
"archivedSubtitle": "Sessions you've archived",
"archivedTitle": "Archived Sessions",
"archivedTitle": "Archived sessions",
"backToActive": "Back to active",
"emptyArchived": "No archived sessions",
"emptyArchivedHint": "Archived sessions will appear here.",
@@ -18,12 +18,12 @@
"emptyNoMatchesHint": "Try a different search term.",
"emptyTitle": "No sessions yet",
"searchArchivedPlaceholder": "Search archived sessions...",
"searchError": "Message search failed. Showing title, agent, and project matches only.",
"searchPlaceholder": "Search conversations",
"searchError": "Message search failed. Showing title, agent, and project matches only. Try again.",
"searchPlaceholder": "Search sessions...",
"searching": "Searching sessions...",
"subtitle": "Browse and search past sessions",
"toggleArchived": "Archived",
"title": "Session History"
"title": "Session history"
},
"search": {
"messageMatches_one": "{{displayCount}} message match",
@@ -1,6 +1,6 @@
{
"about": {
"description": "About information will appear here.",
"description": "View app version, build info, and licenses.",
"title": "About"
},
"appearance": {
@@ -16,18 +16,17 @@
"red": "Red"
},
"description": "Choose your accent color",
"label": "Accent Color"
"label": "Accent color"
},
"density": {
"description": "Adjust the spacing of UI elements",
"label": "Interface Density",
"label": "Interface density",
"options": {
"comfortable": "Comfortable",
"compact": "Compact",
"spacious": "Spacious"
}
},
"description": "Customize the look and feel of Goose",
"theme": {
"description": "Choose your preferred color scheme",
"label": "Theme",
@@ -40,11 +39,11 @@
"title": "Appearance"
},
"chats": {
"description": "Restore archived chats.",
"description": "View and restore your archived chats.",
"empty": "No archived chats.",
"messageCount_one": "{{displayCount}} message",
"messageCount_other": "{{displayCount}} messages",
"sectionTitle": "Archived Chats",
"sectionTitle": "Archived chats",
"title": "Chats",
"types": {
"project": "Project chat",
@@ -70,8 +69,8 @@
"title": "Compaction"
},
"deleteProject": {
"description": "Are you sure you want to permanently delete \"{{name}}\"? This cannot be undone.",
"title": "Delete project permanently?"
"description": "This project and all its data will be permanently removed.",
"title": "Delete \"{{name}}\" permanently?"
},
"extensions": {
"title": "Extensions",
@@ -125,10 +124,10 @@
"agents": "Agents",
"copied": "Copied",
"copyDetails": "Copy details",
"description": "Verify required tools and agent availability for Goose.",
"empty": "No checks are available yet.",
"rerun": "Re-run",
"rerun": "Try again",
"runFix": "Run fix command?",
"runFixDescription": "This will run the following command:",
"running": "Running checks...",
"title": "Doctor",
"tools": "Tools"
@@ -201,12 +200,12 @@
"projects": {
"description": "Manage your projects.",
"empty": "No archived projects.",
"sectionTitle": "Archived Projects",
"sectionTitle": "Archived projects",
"title": "Projects"
},
"providers": {
"agents": {
"description": "Agents handle your requests using their own tools and models",
"description": "Goose is built in to your workspace. Install others to extend your setup.",
"errors": {
"installVerificationFailed": "Install finished but the CLI was not found on PATH. You may need to restart your terminal."
},
@@ -225,10 +224,9 @@
},
"title": "Agent harnesses"
},
"description": "Connect agents and AI models to use with Goose",
"disconnect": "Disconnect",
"models": {
"description": "AI models that power Goose. Expand a provider to review what it needs. Connect signs in with an existing account, while Set up saves API keys or other provider settings.",
"description": "AI models power your agents. Goose requires one to work, but some agents bring their own.",
"notSet": "Not set",
"setup": {
"connected": {
@@ -238,17 +236,17 @@
"oauthDeviceCode": "Connected through Goose device-code sign-in."
},
"fieldDescription": {
"cloudCredentials": "Set up saves any provider details Goose needs here. Your actual authentication still comes from your cloud credentials.",
"configFields": "Set up saves the provider details Goose needs, such as an API key, endpoint, or model settings.",
"hostWithOauthFallback": "Set up saves the host details Goose needs here. You can add a token now, or leave it blank and sign in afterward from Goose.",
"singleApiKey": "Set up saves the API key Goose needs for this provider."
"cloudCredentials": "Add the required details to use this provider.",
"configFields": "Add the required details to use this provider.",
"hostWithOauthFallback": "Add the required details to use this provider.",
"singleApiKey": "Add your API key to use this provider."
},
"hostWithOauthFallbackTerminal": "Leave Access Token blank, save your host URL, then run `goose configure` in your terminal to sign in.",
"nativeConnectDescription": "Sign in with your existing account.",
"pending": {
"cloudCredentials": "Configure your cloud credentials in your terminal environment before using this provider.",
"local": "This provider runs locally and does not need saved settings here.",
"oauthGuided": "Goose will guide you through sign-in from here.",
"local": "This provider runs locally and doesn't need saved settings.",
"oauthGuided": "Goose will guide you through sign-in.",
"oauthTerminal": "Run `goose configure` in your terminal to finish sign-in."
}
},
@@ -13,12 +13,12 @@
"navigation": {
"agents": "Agents",
"home": "Home",
"sessionHistory": "Session History",
"sessionHistory": "Session history",
"skills": "Skills"
},
"search": {
"error": "Message search failed. Showing metadata matches only.",
"placeholder": "Search conversations",
"error": "Message search failed. Showing metadata matches only. Try again.",
"placeholder": "Search chats...",
"searching": "Searching chats..."
},
"sections": {
@@ -1,23 +1,23 @@
{
"dialog": {
"createSkill": "Create Skill",
"createSkill": "Create skill",
"creating": "Creating...",
"description": "Description",
"descriptionPlaceholder": "What it does and when to use it...",
"editTitle": "Edit Skill",
"editTitle": "Edit skill",
"instructions": "Instructions",
"instructionsPlaceholder": "Markdown instructions the agent will follow...",
"name": "Name",
"namePlaceholder": "my-skill-name",
"nameValidation": "Use 164 lowercase letters, numbers, or hyphens. Names cannot start or end with a hyphen.",
"pathOnDisk": "Path on disk",
"newTitle": "New Skill",
"newTitle": "New skill",
"saving": "Saving..."
},
"view": {
"backToSkills": "Back to skills",
"deleteDescription": "Are you sure you want to delete \"{{name}}\"? This cannot be undone.",
"deleteTitle": "Delete skill?",
"deleteDescription": "This skill and its configuration will be permanently removed.",
"deleteTitle": "Delete \"{{name}}\" permanently?",
"category": "Category",
"categories": {
"clear": "Clear categories",
@@ -36,7 +36,7 @@
"writing": "Writing"
}
},
"description": "Skills are reusable instructions that help an agent handle specific tasks, workflows, or tools.",
"description": "Use skills to add specific instructions or behaviors to any agent.",
"detailEmptyDescription": "Select a skill to inspect its source, location, and instructions.",
"detailEmptyTitle": "Choose a skill",
"dropFile": "or drop a file",
@@ -49,8 +49,8 @@
"instructions": "Instructions",
"location": "Location",
"more": "More",
"newSkill": "New Skill",
"noMatchesDescription": "Try a different search or filter.",
"newSkill": "New skill",
"noMatchesDescription": "Try a different search term.",
"noMatchesTitle": "No matching skills",
"openDetails": "Open {{name}} details",
"projects": "Projects",
@@ -68,8 +68,8 @@
"view": {
"copyName": "{{name}} (Copia)",
"deleteFailed": "No se pudo eliminar el agente.",
"deleteDescription": "¿Seguro que quieres eliminar \"{{name}}\"? Esto no se puede deshacer.",
"deleteTitle": "¿Eliminar agente?",
"deleteDescription": "Este agente y su configuración se eliminarán de forma permanente.",
"deleteTitle": "¿Eliminar \"{{name}}\" de forma permanente?",
"deleted": "Se eliminó \"{{name}}\".",
"description": "Configuraciones de agente personalizadas para flujos de trabajo específicos",
"emptyAgentsDescription": "Crea un agente para empezar.",
@@ -45,7 +45,7 @@
"empty": {
"folderNotSet": "Carpeta no configurada",
"noChanges": "No hay cambios sin confirmar",
"noProjectAssigned": "No hay proyecto asignado.",
"noProjectAssigned": "No hay proyecto asignado",
"noExtensions": "No hay extensiones habilitadas",
"noMatchingExtensions": "No hay extensiones que coincidan"
},
@@ -59,7 +59,8 @@
"checkedOutBranch": "{{branch}}",
"currentBranch": "Rama actual",
"dirtyTitle": "Cambios sin confirmar",
"dirtyDescription": "Tienes {{count}} cambio(s) sin confirmar. ¿Cómo deseas proceder?",
"dirtyDescription_one": "Tienes {{count}} cambio sin confirmar. ¿Cómo deseas proceder?",
"dirtyDescription_other": "Tienes {{count}} cambios sin confirmar. ¿Cómo deseas proceder?",
"stashAndSwitch": "Guardar y cambiar",
"carryChanges": "Llevar a la rama",
"cancel": "Cancelar",
@@ -96,19 +97,19 @@
}
},
"files": {
"empty": "Los archivos del proyecto no están disponibles hasta que se asigne un proyecto con directorios de trabajo.",
"empty": "Los archivos aparecerán aquí después de asignar un proyecto.",
"folderEmpty": "La carpeta está vacía",
"loadError": "No se pudo cargar el contenido de la carpeta.",
"loadError": "No se pudo cargar esta carpeta. Inténtalo de nuevo.",
"loading": "Cargando archivos...",
"openFolder": "Abrir carpeta: {{path}}",
"rootLoadError": "No se pudo cargar esta carpeta."
"rootLoadError": "No se pudo cargar esta carpeta. Inténtalo de nuevo."
},
"image": {
"view": "Ver {{label}}"
},
"input": {
"ariaLabel": "Entrada de mensaje del chat",
"placeholder": "Enviar mensaje a {{agent}}, usa @ para mencionar agentes o habilidades"
"placeholder": "Chatea con {{agent}} o usa @ para mencionar un agente"
},
"loading": {
"compacting": "Compactando conversación...",
@@ -46,8 +46,8 @@
"useWorktrees": "Usar git worktrees para aislar ramas"
},
"view": {
"deleteDescription": "¿Seguro que quieres eliminar \"{{name}}\"? Esto no se puede deshacer.",
"deleteTitle": "¿Eliminar proyecto?",
"deleteDescription": "Este proyecto y todos sus datos se eliminarán de forma permanente.",
"deleteTitle": "¿Eliminar \"{{name}}\" de forma permanente?",
"description": "Organiza tu trabajo en contextos de proyecto enfocados",
"emptyDescription": "Crea un proyecto para organizar tu trabajo.",
"emptyTitle": "Aún no hay proyectos",
@@ -18,8 +18,8 @@
"emptyNoMatchesHint": "Prueba con otro término de búsqueda.",
"emptyTitle": "Aún no hay sesiones",
"searchArchivedPlaceholder": "Buscar sesiones archivadas...",
"searchError": "La búsqueda de mensajes falló. Mostrando solo coincidencias por título, agente y proyecto.",
"searchPlaceholder": "Buscar conversaciones",
"searchError": "La búsqueda de mensajes falló. Mostrando solo coincidencias por título, agente y proyecto. Inténtalo de nuevo.",
"searchPlaceholder": "Buscar sesiones...",
"searching": "Buscando sesiones...",
"subtitle": "Explora y busca sesiones anteriores",
"toggleArchived": "Archivadas",
@@ -27,7 +27,6 @@
"spacious": "Espaciosa"
}
},
"description": "Personaliza el aspecto de Goose",
"theme": {
"description": "Elige tu esquema de color preferido",
"label": "Tema",
@@ -70,8 +69,8 @@
"title": "Compactación"
},
"deleteProject": {
"description": "¿Seguro que quieres eliminar permanentemente \"{{name}}\"? Esta acción no se puede deshacer.",
"title": "¿Eliminar el proyecto de forma permanente?"
"description": "Este proyecto y todos sus datos se eliminarán de forma permanente.",
"title": "¿Eliminar \"{{name}}\" de forma permanente?"
},
"extensions": {
"title": "Extensiones",
@@ -125,10 +124,10 @@
"agents": "Agentes",
"copied": "Copiado",
"copyDetails": "Copiar detalles",
"description": "Verifica las herramientas necesarias y la disponibilidad de agentes para Goose.",
"empty": "Todavía no hay comprobaciones disponibles.",
"rerun": "Volver a ejecutar",
"runFix": "¿Ejecutar el comando de corrección?",
"rerun": "Volver a intentar",
"runFix": "¿Ejecutar corrección?",
"runFixDescription": "Esto ejecutará el siguiente comando:",
"running": "Ejecutando comprobaciones...",
"title": "Diagnóstico",
"tools": "Herramientas"
@@ -206,7 +205,7 @@
},
"providers": {
"agents": {
"description": "Los agentes manejan tus solicitudes usando sus propias herramientas y modelos",
"description": "Goose está integrado en tu espacio de trabajo. Instala otros para ampliar tu configuración.",
"errors": {
"installVerificationFailed": "La instalación terminó, pero no se encontró la CLI en PATH. Puede que tengas que reiniciar tu terminal."
},
@@ -225,10 +224,9 @@
},
"title": "Arneses de agentes"
},
"description": "Conecta agentes y modelos de IA para usar con Goose",
"disconnect": "Desconectar",
"models": {
"description": "Modelos de IA que alimentan a Goose. Expande un proveedor para revisar lo que necesita. Conectar inicia sesión con una cuenta existente, mientras que Configurar guarda claves API u otros ajustes del proveedor.",
"description": "Necesitas al menos un proveedor de modelos para usar el agente Goose. Algunos agentes pueden traer sus propias conexiones de modelo.",
"notSet": "No configurado",
"setup": {
"connected": {
@@ -238,17 +236,17 @@
"oauthDeviceCode": "Conectado mediante el inicio de sesión por código de dispositivo de Goose."
},
"fieldDescription": {
"cloudCredentials": "La configuración guarda aquí cualquier detalle del proveedor que Goose necesite. Tu autenticación real sigue viniendo de tus credenciales de nube.",
"configFields": "La configuración guarda los detalles del proveedor que Goose necesita, como una clave API, un endpoint o ajustes del modelo.",
"hostWithOauthFallback": "La configuración guarda aquí los detalles del host que Goose necesita. Puedes añadir un token ahora o dejarlo en blanco e iniciar sesión después desde Goose.",
"singleApiKey": "La configuración guarda la clave API que Goose necesita para este proveedor."
"cloudCredentials": "Agrega los datos requeridos para usar este proveedor.",
"configFields": "Agrega los datos requeridos para usar este proveedor.",
"hostWithOauthFallback": "Agrega los datos requeridos para usar este proveedor.",
"singleApiKey": "Agrega tu clave API para usar este proveedor."
},
"hostWithOauthFallbackTerminal": "Deja en blanco el token de acceso, guarda la URL del host y luego ejecuta `goose configure` en tu terminal para iniciar sesión.",
"nativeConnectDescription": "Inicia sesión con tu cuenta existente.",
"pending": {
"cloudCredentials": "Configura tus credenciales de nube en el entorno de tu terminal antes de usar este proveedor.",
"local": "Este proveedor se ejecuta localmente y no necesita ajustes guardados aquí.",
"oauthGuided": "Goose te guiará por el inicio de sesión desde aquí.",
"local": "Este proveedor se ejecuta localmente y no necesita ajustes guardados.",
"oauthGuided": "Goose te guiará por el inicio de sesión.",
"oauthTerminal": "Ejecuta `goose configure` en tu terminal para terminar de iniciar sesión."
}
},
@@ -17,8 +17,8 @@
"skills": "Habilidades"
},
"search": {
"error": "La búsqueda de mensajes falló. Mostrando solo coincidencias de metadatos.",
"placeholder": "Buscar conversaciones",
"error": "La búsqueda de mensajes falló. Mostrando solo coincidencias de metadatos. Inténtalo de nuevo.",
"placeholder": "Buscar chats...",
"searching": "Buscando chats..."
},
"sections": {
@@ -34,8 +34,8 @@
"writing": "Redacción"
}
},
"deleteDescription": "¿Seguro que quieres eliminar \"{{name}}\"? Esto no se puede deshacer.",
"deleteTitle": "¿Eliminar skill?",
"deleteDescription": "Esta habilidad y su configuración se eliminarán de forma permanente.",
"deleteTitle": "¿Eliminar \"{{name}}\" de forma permanente?",
"description": "Las skills son instrucciones reutilizables que ayudan a un agente a manejar tareas, flujos de trabajo o herramientas específicas.",
"detailEmptyDescription": "Selecciona una skill para inspeccionar su origen, ubicación e instrucciones.",
"detailEmptyTitle": "Elige una skill",
+13 -5
View File
@@ -205,9 +205,13 @@ test.describe("Agents view", () => {
await card.getByLabel("Agent options").click();
await page.getByRole("menuitem", { name: "Delete" }).click();
await expect(page.getByText("Delete agent?")).toBeVisible();
await expect(
page.getByText(/Are you sure you want to delete.*Code Reviewer/),
page.getByText('Delete "Code Reviewer" permanently?'),
).toBeVisible();
await expect(
page.getByText(
"This agent and its configuration will be permanently removed.",
),
).toBeVisible();
await expect(page.getByRole("button", { name: "Cancel" })).toBeVisible();
await expect(page.getByRole("button", { name: "Delete" })).toBeVisible();
@@ -221,14 +225,18 @@ test.describe("Agents view", () => {
const card = page.getByLabel("Agent: Code Reviewer");
await card.getByLabel("Agent options").click();
await page.getByRole("menuitem", { name: "Delete" }).click();
await expect(page.getByText("Delete agent?")).toBeVisible();
await expect(
page.getByText('Delete "Code Reviewer" permanently?'),
).toBeVisible();
const confirmDialog = page.locator(".max-w-sm", {
has: page.getByText("Delete agent?"),
has: page.getByText('Delete "Code Reviewer" permanently?'),
});
await confirmDialog.getByRole("button", { name: "Cancel" }).click();
await expect(page.getByText("Delete agent?")).not.toBeVisible();
await expect(
page.getByText('Delete "Code Reviewer" permanently?'),
).not.toBeVisible();
await expect(page.getByLabel("Agent: Code Reviewer")).toBeVisible();
});
+2 -2
View File
@@ -13,10 +13,10 @@ test.describe("Skills view", () => {
await expect(page.locator("h1", { hasText: "Skills" })).toBeVisible();
await expect(
page.getByText(/Skills are reusable instructions/),
page.getByText(/Use skills to add specific instructions/),
).toBeVisible();
await expect(page.getByRole("button", { name: "Import" })).toBeVisible();
await expect(page.getByRole("button", { name: "New Skill" })).toBeVisible();
await expect(page.getByRole("button", { name: "New skill" })).toBeVisible();
});
test("shows skills in the list and opens a dedicated detail page", async ({
+1 -1
View File
@@ -21,7 +21,7 @@ test.describe("Smoke tests", () => {
await page.goto("/");
await expect(
page.getByPlaceholder(/Message .*, @ to mention agents/),
page.getByPlaceholder(/Chat with .* or @ mention an agent/),
).toBeVisible({ timeout: 10_000 });
});
});