mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-29 02:19:19 +02:00
Compare commits
1 Commits
codex/agen
...
agent/j/31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c205a4ba5c |
@@ -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",
|
||||
|
||||
@@ -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": "ランタイムの所有者とワークスペース管理者のみが、このランタイムを削除できます",
|
||||
|
||||
@@ -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": "런타임 소유자와 워크스페이스 관리자만 이 런타임을 삭제할 수 있습니다",
|
||||
|
||||
@@ -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": "只有运行时所有者和工作区管理员可以删除这个运行时",
|
||||
|
||||
@@ -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<typeof useT<"runtimes">>["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 }) => (
|
||||
<CliCell
|
||||
runtime={row.original.runtime}
|
||||
latestCliVersion={latestCliVersion}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => <CliCell runtime={row.original.runtime} />,
|
||||
},
|
||||
{
|
||||
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 <span className="text-xs text-muted-foreground/50">—</span>;
|
||||
}
|
||||
const meta = runtime.metadata as Record<string, unknown> | 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 <span className="text-xs text-muted-foreground/50">—</span>;
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div className="flex min-w-0 items-center gap-1 text-xs">
|
||||
<span
|
||||
className={`truncate font-mono ${
|
||||
hasUpdate ? "text-warning" : "text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{cliVersion}
|
||||
<div className="flex min-w-0 items-center text-xs">
|
||||
<span className="truncate font-mono text-muted-foreground">
|
||||
{version}
|
||||
</span>
|
||||
{hasUpdate && latestCliVersion && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<ArrowUpCircle
|
||||
className="h-3 w-3 shrink-0 text-warning"
|
||||
aria-label={t(($) => $.list.cli_update_available_aria)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t(($) => $.list.cli_update_available_tooltip, { version: latestCliVersion })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<string>;
|
||||
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({
|
||||
|
||||
@@ -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<typeof cli.cell>[0]);
|
||||
return <>{cell}</>;
|
||||
}
|
||||
|
||||
return render(
|
||||
<I18nProvider locale="en" resources={TEST_RESOURCES}>
|
||||
<QueryClientProvider client={qc}>
|
||||
<Harness />
|
||||
</QueryClientProvider>
|
||||
</I18nProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user