Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions apps/web/src/routes/agent/lifecycle/kind-fork-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@ import {

import type { AgentKind } from "../agent.types";

const KIND_LABELS: Record<AgentKind, { title: string; icon: typeof Bot }> = {
pet: { title: "Assistant Agent", icon: Bot },
cattle: { title: "Task Agent", icon: Zap },
};

const KIND_TAGLINE_KEYS: Record<AgentKind, string> = {
pet: "agentLifecycle.kindPetTagline",
cattle: "agentLifecycle.kindCattleTagline",
const KIND_COPY: Record<AgentKind, { titleKey: string; taglineKey: string; icon: typeof Bot }> = {
pet: { titleKey: "agent.assistantAgent", taglineKey: "agent.kindAssistantTagline", icon: Bot },
cattle: { titleKey: "agent.taskAgent", taglineKey: "agent.kindTaskTagline", icon: Zap },
};

const CARRIED_OVER = [
Expand Down Expand Up @@ -60,12 +55,14 @@ export function KindForkDialog({
onConfirm: () => void;
}) {
const { t } = useTranslation();
const current = KIND_LABELS[currentKind];
const target = KIND_LABELS[targetKind];
const current = KIND_COPY[currentKind];
const target = KIND_COPY[targetKind];
const CurrentIcon = current.icon;
const TargetIcon = target.icon;
const currentTagline = t(KIND_TAGLINE_KEYS[currentKind]);
const targetTagline = t(KIND_TAGLINE_KEYS[targetKind]);
const currentTitle = t(current.titleKey);
const targetTitle = t(target.titleKey);
const currentTagline = t(current.taglineKey);
const targetTagline = t(target.taglineKey);
const isPetToCattle = currentKind === "pet" && targetKind === "cattle";
const dropped = isPetToCattle ? DROPPED_PET_TO_CATTLE.map((key) => t(key)) : [];
const added = !isPetToCattle ? ADDED_CATTLE_TO_PET.map((key) => t(key)) : [];
Expand All @@ -76,15 +73,15 @@ export function KindForkDialog({
<DialogHeader>
<DialogTitle className="text-[15px]">{t("agentLifecycle.forkTitle")}</DialogTitle>
<DialogDescription className="text-fg-2 text-[12.5px] leading-relaxed">
{t("agentLifecycle.forkDescription", { agentName, target: target.title })}
{t("agentLifecycle.forkDescription", { agentName, target: targetTitle })}
</DialogDescription>
</DialogHeader>

<div className="border-border-subtle bg-bg-1 rounded-lg border px-3 py-2.5">
<div className="flex items-center gap-2 text-[12.5px]">
<KindChip icon={CurrentIcon} label={current.title} tagline={currentTagline} muted />
<KindChip icon={CurrentIcon} label={currentTitle} tagline={currentTagline} muted />
<ArrowRight className="text-fg-3 size-4 shrink-0" />
<KindChip icon={TargetIcon} label={target.title} tagline={targetTagline} />
<KindChip icon={TargetIcon} label={targetTitle} tagline={targetTagline} />
</div>
</div>

Expand Down Expand Up @@ -127,9 +124,7 @@ export function KindForkDialog({
{t("common.cancel")}
</Button>
<Button disabled={busy} onClick={onConfirm} size="sm">
{busy
? t("agentLifecycle.forking")
: t("agentLifecycle.forkAs", { title: target.title })}
{busy ? t("agentLifecycle.forking") : t("agentLifecycle.forkAs", { kind: targetTitle })}
</Button>
</DialogFooter>
</DialogContent>
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/shared/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@
"apiTokenSettingsLocation": "Create tokens in Settings → Access tokens.",
"applyChanges": "Apply changes",
"applying": "Applying…",
"assistantSandboxState": "Assistant sandbox state",
"checkpointedPathsRestored": "checkpointed paths restored",
"copiedToClipboard": "Copied to clipboard",
"copyInstructionDescription": "Copy instructions for this Agent.",
Expand All @@ -1125,12 +1126,18 @@
"fieldsEdited": "{{count}} fields edited",
"fieldsInChange": "Fields in this change",
"forkAdded": "Added to fork",
"forkAddedSandbox": "Dedicated stable sandbox",
"forkAs": "Fork as {{kind}}",
"forkCarriedEnvVars": "Environment variables",
"forkCarriedManifestFields": "Name, description, model, and system prompt",
"forkCarriedMcpBindings": "MCP server bindings",
"forkCarriedOver": "Carried over",
"forkDescription": "Create a new Agent with this type.",
"forkDropped": "Dropped",
"forkNotWiredNotice": "Fork action is not wired yet.",
"forkStaysHere": "Stays on this Agent",
"forkStaysRuntimeLogs": "Runtime logs",
"forkStaysSessions": "Sessions and transcripts",
"forkTitle": "Change Agent type",
"forking": "Forking…",
"kindHintAssistant": "Assistant Agent",
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/shared/i18n/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@
"apiTokenSettingsLocation": "設定 → アクセストークンでトークンを作成します。",
"applyChanges": "変更を適用",
"applying": "適用中…",
"assistantSandboxState": "アシスタントのサンドボックス状態",
"checkpointedPathsRestored": "チェックポイントのパスを復元",
"copiedToClipboard": "クリップボードにコピーしました",
"copyInstructionDescription": "このエージェントの手順をコピーします。",
Expand All @@ -1125,12 +1126,18 @@
"fieldsEdited": "{{count}} 個のフィールドを編集",
"fieldsInChange": "この変更のフィールド",
"forkAdded": "フォークに追加",
"forkAddedSandbox": "専用の安定サンドボックス",
"forkAs": "{{kind}} としてフォーク",
"forkCarriedEnvVars": "環境変数",
"forkCarriedManifestFields": "名前、説明、モデル、システムプロンプト",
"forkCarriedMcpBindings": "MCP サーバーのバインディング",
"forkCarriedOver": "引き継ぎ",
"forkDescription": "このタイプで新しいエージェントを作成します。",
"forkDropped": "破棄",
"forkNotWiredNotice": "フォーク操作はまだ接続されていません。",
"forkStaysHere": "このエージェントに残る",
"forkStaysRuntimeLogs": "ランタイムログ",
"forkStaysSessions": "セッションとトランスクリプト",
"forkTitle": "エージェントタイプを変更",
"forking": "フォーク中…",
"kindHintAssistant": "アシスタントエージェント",
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/shared/i18n/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@
"apiTokenSettingsLocation": "在设置 → 访问令牌中创建令牌。",
"applyChanges": "应用更改",
"applying": "应用中…",
"assistantSandboxState": "助理沙箱状态",
"checkpointedPathsRestored": "已恢复检查点路径",
"copiedToClipboard": "已复制到剪贴板",
"copyInstructionDescription": "复制此智能体的使用说明。",
Expand All @@ -1125,12 +1126,18 @@
"fieldsEdited": "已编辑 {{count}} 个字段",
"fieldsInChange": "此次更改中的字段",
"forkAdded": "已添加到派生智能体",
"forkAddedSandbox": "专属稳定沙箱",
"forkAs": "派生为 {{kind}}",
"forkCarriedEnvVars": "环境变量",
"forkCarriedManifestFields": "名称、描述、模型和系统提示词",
"forkCarriedMcpBindings": "MCP 服务器绑定",
"forkCarriedOver": "已保留",
"forkDescription": "使用此类型创建新的智能体。",
"forkDropped": "已丢弃",
"forkNotWiredNotice": "派生操作尚未接入。",
"forkStaysHere": "保留在此智能体",
"forkStaysRuntimeLogs": "运行时日志",
"forkStaysSessions": "会话和记录",
"forkTitle": "更改智能体类型",
"forking": "派生中…",
"kindHintAssistant": "助手智能体",
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/shared/i18n/translations/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@
"apiTokenSettingsLocation": "在設定 → 存取權杖中建立權杖。",
"applyChanges": "套用變更",
"applying": "套用中…",
"assistantSandboxState": "助理沙箱狀態",
"checkpointedPathsRestored": "已還原檢查點路徑",
"copiedToClipboard": "已複製到剪貼簿",
"copyInstructionDescription": "複製此智慧體的使用說明。",
Expand All @@ -1125,12 +1126,18 @@
"fieldsEdited": "已編輯 {{count}} 個欄位",
"fieldsInChange": "此變更中的欄位",
"forkAdded": "已新增至分支智慧體",
"forkAddedSandbox": "專屬穩定沙箱",
"forkAs": "分支為 {{kind}}",
"forkCarriedEnvVars": "環境變數",
"forkCarriedManifestFields": "名稱、描述、模型和系統提示",
"forkCarriedMcpBindings": "MCP 伺服器綁定",
"forkCarriedOver": "已沿用",
"forkDescription": "使用此類型建立新的智慧體。",
"forkDropped": "已捨棄",
"forkNotWiredNotice": "分支操作尚未接入。",
"forkStaysHere": "保留在此智慧體",
"forkStaysRuntimeLogs": "執行日誌",
"forkStaysSessions": "工作階段和對話紀錄",
"forkTitle": "變更智慧體類型",
"forking": "分支中…",
"kindHintAssistant": "助理智慧體",
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/shared/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ function DialogContent({
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 sm:max-w-lg",
// grid-cols-[minmax(0,1fr)]: the implicit auto column would floor at
// the widest child's min-content (nowrap truncation targets, content
// sized textareas), letting content grow past the dialog's own width.
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] grid-cols-[minmax(0,1fr)] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 sm:max-w-lg",
className,
)}
{...props}
Expand Down
32 changes: 32 additions & 0 deletions apps/web/tests/reported-i18n-layout-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,38 @@ describe("reported i18n and dialog layout regressions", () => {
}
});

test("resolves every Agent kind fork dialog key in every locale", () => {
const source = readSource("../src/routes/agent/lifecycle/kind-fork-dialog.tsx");
const referencedKeys = [
...source.matchAll(/"((?:agent|agentLifecycle|environments|common)\.[A-Za-z0-9]+)"/gu),
].map((match) => match[1] ?? "");

expect(referencedKeys.length).toBeGreaterThan(0);

for (const catalog of [en, zhCN, zhTW, ja]) {
for (const key of referencedKeys) {
const value = lookup(catalog, key);
expect(typeof value).toBe("string");
expect(value).not.toBe(key);
}
}
});

test("passes the {{kind}} variable the fork confirm label interpolates", () => {
const source = readSource("../src/routes/agent/lifecycle/kind-fork-dialog.tsx");

expect(source).toContain('t("agentLifecycle.forkAs", { kind:');
for (const catalog of [en, zhCN, zhTW, ja]) {
expect(lookup(catalog, "agentLifecycle.forkAs")).toContain("{{kind}}");
}
});

test("keeps dialog grid columns shrinkable so content cannot outgrow the dialog", () => {
const source = readSource("../src/shared/ui/dialog.tsx");

expect(source).toContain("grid-cols-[minmax(0,1fr)]");
});

test("does not render contract-owned Agent kind English copy directly", () => {
const source = readSource("../src/routes/agent/components/kind-selector.tsx");

Expand Down
Loading