diff --git a/packages/views/locales/en/runtimes.json b/packages/views/locales/en/runtimes.json index e3c14a4cc..45da062d3 100644 --- a/packages/views/locales/en/runtimes.json +++ b/packages/views/locales/en/runtimes.json @@ -262,8 +262,6 @@ "col_cli": "CLI", "cost_delta_flat": "flat", "cli_managed_badge": "Desktop", - "cli_update_available_aria": "Update available", - "cli_update_available_tooltip": "Update available: {{version}}", "row_actions_aria": "Row actions", "delete_action": "Delete", "delete_permission_hint": "Only the runtime owner and workspace admins can delete this runtime", diff --git a/packages/views/locales/ja/runtimes.json b/packages/views/locales/ja/runtimes.json index d157defc8..622f2f78d 100644 --- a/packages/views/locales/ja/runtimes.json +++ b/packages/views/locales/ja/runtimes.json @@ -250,8 +250,6 @@ "col_cli": "CLI", "cost_delta_flat": "変化なし", "cli_managed_badge": "デスクトップ", - "cli_update_available_aria": "更新あり", - "cli_update_available_tooltip": "更新あり: {{version}}", "row_actions_aria": "行の操作", "delete_action": "削除", "delete_permission_hint": "ランタイムの所有者とワークスペース管理者のみが、このランタイムを削除できます", diff --git a/packages/views/locales/ko/runtimes.json b/packages/views/locales/ko/runtimes.json index df03352a8..49b398ff0 100644 --- a/packages/views/locales/ko/runtimes.json +++ b/packages/views/locales/ko/runtimes.json @@ -262,8 +262,6 @@ "col_cli": "CLI", "cost_delta_flat": "변화 없음", "cli_managed_badge": "데스크톱", - "cli_update_available_aria": "업데이트 가능", - "cli_update_available_tooltip": "업데이트 가능: {{version}}", "row_actions_aria": "행 작업", "delete_action": "삭제", "delete_permission_hint": "런타임 소유자와 워크스페이스 관리자만 이 런타임을 삭제할 수 있습니다", diff --git a/packages/views/locales/zh-Hans/runtimes.json b/packages/views/locales/zh-Hans/runtimes.json index 82cd9ba28..4e0d6ca52 100644 --- a/packages/views/locales/zh-Hans/runtimes.json +++ b/packages/views/locales/zh-Hans/runtimes.json @@ -250,8 +250,6 @@ "col_cli": "CLI", "cost_delta_flat": "持平", "cli_managed_badge": "桌面端", - "cli_update_available_aria": "有可用更新", - "cli_update_available_tooltip": "有可用更新:{{version}}", "row_actions_aria": "行操作", "delete_action": "删除", "delete_permission_hint": "只有运行时所有者和工作区管理员可以删除这个运行时", diff --git a/packages/views/runtimes/components/runtime-columns.tsx b/packages/views/runtimes/components/runtime-columns.tsx index 8afb7b325..4d9562bcb 100644 --- a/packages/views/runtimes/components/runtime-columns.tsx +++ b/packages/views/runtimes/components/runtime-columns.tsx @@ -1,12 +1,7 @@ "use client"; import { useMemo, useState } from "react"; -import { - ArrowUpCircle, - Globe, - MoreHorizontal, - Trash2, -} from "lucide-react"; +import { Globe, MoreHorizontal, Trash2 } from "lucide-react"; import { toast } from "sonner"; import type { ColumnDef } from "@tanstack/react-table"; import { useQuery } from "@tanstack/react-query"; @@ -38,7 +33,6 @@ import { computeCostInWindow, formatLastSeen, isSelfHealingRuntime, - isVersionNewer, pctChange, } from "../utils"; import { splitRuntimeName } from "./runtime-machines"; @@ -78,7 +72,6 @@ type RuntimesT = ReturnType>["t"]; interface CreateColumnsArgs { showOwner: boolean; - latestCliVersion: string | null; wsId: string; now: number; t: RuntimesT; @@ -86,7 +79,6 @@ interface CreateColumnsArgs { export function createRuntimeColumns({ showOwner, - latestCliVersion, wsId, now, t, @@ -167,12 +159,7 @@ export function createRuntimeColumns({ id: "cli", header: () => t(($) => $.list.col_cli), size: COL_WIDTHS.cli, - cell: ({ row }) => ( - - ), + cell: ({ row }) => , }, { id: "actions", @@ -373,60 +360,30 @@ function CostCell({ runtimeId }: { runtimeId: string }) { ); } -function CliCell({ - runtime, - latestCliVersion, -}: { - runtime: AgentRuntime; - latestCliVersion: string | null; -}) { - const { t } = useT("runtimes"); +function CliCell({ runtime }: { runtime: AgentRuntime }) { if (runtime.runtime_mode === "cloud") { return ; } const meta = runtime.metadata as Record | null; - const cliVersion = - meta && typeof meta.cli_version === "string" ? meta.cli_version : null; - const launchedBy = - meta && typeof meta.launched_by === "string" ? meta.launched_by : null; - const isManaged = launchedBy === "desktop"; + // `version` is the agent's own underlying CLI tool version — distinct per + // provider (e.g. "2.1.5 (Claude Code)", "codex-cli 0.118.0", "0.42.0"). + // The separate `cli_version` is the shared multica daemon CLI, identical + // for every runtime on one machine; surfacing it here made all agents + // show the same number (#3838). The daemon CLI version and its update + // prompt belong to the machine — they live in the machine meta strip and + // the detail page's UpdateSection, not on a per-agent row. + const version = + meta && typeof meta.version === "string" ? meta.version : null; - if (!cliVersion) { + if (!version) { return ; } - // Desktop-managed daemons can never self-update from this page (the - // Electron app ships and replaces the binary), so the upgrade marker - // would lie — suppress regardless of version comparison. - const hasUpdate = - !isManaged && - !!latestCliVersion && - isVersionNewer(latestCliVersion, cliVersion); - return ( -
- - {cliVersion} +
+ + {version} - {hasUpdate && latestCliVersion && ( - - $.list.cli_update_available_aria)} - /> - } - /> - - {t(($) => $.list.cli_update_available_tooltip, { version: latestCliVersion })} - - - )}
); } diff --git a/packages/views/runtimes/components/runtime-list.tsx b/packages/views/runtimes/components/runtime-list.tsx index c63a8336e..798805398 100644 --- a/packages/views/runtimes/components/runtime-list.tsx +++ b/packages/views/runtimes/components/runtime-list.tsx @@ -15,7 +15,6 @@ import { agentListOptions, memberListOptions, } from "@multica/core/workspace/queries"; -import { latestCliVersionOptions } from "@multica/core/runtimes"; import { agentTaskSnapshotOptions } from "@multica/core/agents"; import { paths, useWorkspaceSlug } from "@multica/core/paths"; import { DataTable } from "@multica/ui/components/ui/data-table"; @@ -76,10 +75,11 @@ export function RuntimeList({ now, }: { runtimes: AgentRuntime[]; - // Kept on the API surface for callers — the CLI column re-derives - // update state per row via metadata.cli_version + the GitHub-release - // query, so this prop is now unused. Left to avoid scope creep on the - // page-level wrapper that still computes the set. + // Kept on the API surface for callers, but unused here: the CLI column + // shows each agent's own tool version, while the multica daemon CLI + // update prompt lives at the machine/detail level (UpdateSection), so the + // table no longer derives per-row update state. Left to avoid scope creep + // on the page-level wrapper that still computes the set. updatableIds?: Set; now: number; }) { @@ -94,7 +94,6 @@ export function RuntimeList({ const { data: agents = [] } = useQuery(agentListOptions(wsId)); const { data: members = [] } = useQuery(memberListOptions(wsId)); const { data: snapshot = [] } = useQuery(agentTaskSnapshotOptions(wsId)); - const { data: latestCliVersion = null } = useQuery(latestCliVersionOptions()); const currentMember = user ? members.find((m) => m.user_id === user.id) @@ -140,12 +139,11 @@ export function RuntimeList({ () => createRuntimeColumns({ showOwner, - latestCliVersion, wsId, now, t, }), - [showOwner, latestCliVersion, wsId, now, t], + [showOwner, wsId, now, t], ); const table = useReactTable({ diff --git a/packages/views/runtimes/components/runtime-row-menu.test.tsx b/packages/views/runtimes/components/runtime-row-menu.test.tsx index 3a9a4d2fd..57fc6a010 100644 --- a/packages/views/runtimes/components/runtime-row-menu.test.tsx +++ b/packages/views/runtimes/components/runtime-row-menu.test.tsx @@ -121,7 +121,6 @@ function renderActionsCell(row: RuntimeRow) { const { t } = useT("runtimes"); const columns = createRuntimeColumns({ showOwner: false, - latestCliVersion: null, wsId: "ws-1", now: Date.now(), t, @@ -187,3 +186,80 @@ describe("runtime list row menu", () => { expect(screen.queryByLabelText("Row actions")).not.toBeInTheDocument(); }); }); + +// The CLI column lives in its own cell renderer; resolve it from +// createRuntimeColumns and render it in isolation, mirroring renderActionsCell. +function renderCliCell(row: RuntimeRow) { + const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + + function Harness() { + const { t } = useT("runtimes"); + const columns = createRuntimeColumns({ + showOwner: false, + wsId: "ws-1", + now: Date.now(), + t, + }); + const cli = columns.find((c) => c.id === "cli"); + if (!cli || typeof cli.cell !== "function") { + throw new Error("cli column missing or has no cell renderer"); + } + const cell = cli.cell({ + row: { original: row }, + } as unknown as Parameters[0]); + return <>{cell}; + } + + return render( + + + + + , + ); +} + +describe("runtime list CLI column", () => { + beforeEach(() => vi.clearAllMocks()); + + // #3838: every agent showed the same number because the column rendered the + // shared multica daemon `cli_version`. It must instead show the agent's own + // tool version from `metadata.version`. + it("shows the agent's own CLI tool version, not the shared daemon version", () => { + renderCliCell( + makeRow( + makeRuntime({ + runtime_mode: "local", + metadata: { version: "2.1.5 (Claude Code)", cli_version: "0.3.17" }, + }), + ), + ); + expect(screen.getByText("2.1.5 (Claude Code)")).toBeInTheDocument(); + expect(screen.queryByText("0.3.17")).not.toBeInTheDocument(); + }); + + it("falls back to an em dash when the agent version is missing", () => { + renderCliCell( + makeRow( + makeRuntime({ + runtime_mode: "local", + metadata: { cli_version: "0.3.17" }, + }), + ), + ); + expect(screen.queryByText("0.3.17")).not.toBeInTheDocument(); + expect(screen.getByText("—")).toBeInTheDocument(); + }); + + it("renders an em dash for cloud runtimes", () => { + renderCliCell( + makeRow( + makeRuntime({ + runtime_mode: "cloud", + metadata: { version: "2.1.5 (Claude Code)" }, + }), + ), + ); + expect(screen.getByText("—")).toBeInTheDocument(); + }); +});