Compare commits

...

2 Commits

Author SHA1 Message Date
Lambda
70b245cb7a fix(issues): header chip shows 'is queued' when no agent is running
Co-authored-by: multica-agent <github@multica.ai>
2026-06-09 02:39:35 +08:00
Lambda
a02934e2d9 feat(issues): add brand border beam to active agent header chip
Co-authored-by: multica-agent <github@multica.ai>
2026-06-09 02:01:09 +08:00
5 changed files with 43 additions and 13 deletions

View File

@@ -9,6 +9,7 @@ import {
} from "@multica/ui/components/ui/popover";
import { useWorkspaceId } from "@multica/core/hooks";
import { useActorName } from "@multica/core/workspace/hooks";
import { cn } from "@multica/ui/lib/utils";
import { agentTaskSnapshotOptions } from "@multica/core/agents";
import type { AgentTask } from "@multica/core/types";
import { AgentAvatarStack } from "../../agents/components/agent-avatar-stack";
@@ -26,9 +27,10 @@ import { useT } from "../../i18n";
// with those surfaces and costs zero extra network.
//
// Collapsed display stays intentionally shallow:
// - one active agent → avatar + "{name} is working"
// - multiple agents → avatar stack + "N agents working"
// - queued only → same copy, half-opacity avatars / muted text
// - one running agent → avatar + "{name} is working"
// - multiple running → avatar stack + "N agents working"
// - queued only "{name} is queued" / "N agents queued",
// half-opacity avatars / muted text (no beam)
//
// Click opens a compact Popover card with the same active rows as the right
// panel. Those rows show necessary status/time and task entry actions, but do
@@ -83,13 +85,23 @@ function ActiveChip({ issueId, running, queued }: ActiveChipProps) {
const agentIds = [...new Set(activeTasks.map((task) => task.agent_id))];
const anyRunning = running.length > 0;
const isSingle = agentIds.length === 1;
// Copy must follow the actual state: "is working" only when something is
// truly running. With nothing running (queued / dispatched / parked on a
// path lock) the chip reads "is queued" so a not-yet-started agent isn't
// mislabelled as working.
const label = isSingle
? t(($) => $.agent_live.is_working, {
name: getActorName("agent", agentIds[0] ?? ""),
})
: t(($) => $.agent_activity.hover_header, {
count: agentIds.length,
});
? t(
($) =>
anyRunning ? $.agent_live.is_working : $.agent_live.is_queued,
{ name: getActorName("agent", agentIds[0] ?? "") },
)
: t(
($) =>
anyRunning
? $.agent_activity.hover_header
: $.agent_activity.hover_header_queued,
{ count: agentIds.length },
);
return (
<div className="flex items-center gap-1">
@@ -99,7 +111,15 @@ function ActiveChip({ issueId, running, queued }: ActiveChipProps) {
<button
type="button"
aria-label={label}
className="flex h-7 max-w-[11rem] items-center gap-1.5 rounded-md px-1.5 text-muted-foreground outline-none transition-colors hover:bg-accent/60 focus-visible:ring-2 focus-visible:ring-ring"
// While an agent is actively running, the chip wears the
// brand border beam — a highlight sweeping around its rounded
// edge — so a triggered run is unmistakably "alive" in the
// header. Queued-only state stays calm (no beam) to reserve the
// motion for work that is genuinely in flight.
className={cn(
"flex h-7 max-w-[11rem] items-center gap-1.5 rounded-md px-1.5 text-muted-foreground outline-none transition-colors hover:bg-accent/60 focus-visible:ring-2 focus-visible:ring-ring",
anyRunning && "border-beam bg-brand/5",
)}
/>
}
>
@@ -117,9 +137,13 @@ function ActiveChip({ issueId, running, queued }: ActiveChipProps) {
</PopoverTrigger>
<PopoverContent align="end" keepMounted className="w-80">
<div className="text-xs font-medium text-muted-foreground">
{t(($) => $.agent_activity.hover_header, {
count: agentIds.length,
})}
{t(
($) =>
anyRunning
? $.agent_activity.hover_header
: $.agent_activity.hover_header_queued,
{ count: agentIds.length },
)}
</div>
<div className="flex flex-col gap-0.5">
{activeTasks.map((task) => (

View File

@@ -293,6 +293,8 @@
"agent_activity": {
"hover_header_one": "{{count}} agent working",
"hover_header_other": "{{count}} agents working",
"hover_header_queued_one": "{{count}} agent queued",
"hover_header_queued_other": "{{count}} agents queued",
"status_running": "Working",
"status_queued": "Queued",
"chip_label": "working",

View File

@@ -282,6 +282,7 @@
},
"agent_activity": {
"hover_header_other": "作業中のエージェント {{count}} 件",
"hover_header_queued_other": "待機中のエージェント {{count}} 件",
"status_running": "作業中",
"status_queued": "待機中",
"chip_label": "作業中",

View File

@@ -293,6 +293,8 @@
"agent_activity": {
"hover_header_one": "작업 중인 에이전트 {{count}}개",
"hover_header_other": "작업 중인 에이전트 {{count}}개",
"hover_header_queued_one": "대기 중인 에이전트 {{count}}개",
"hover_header_queued_other": "대기 중인 에이전트 {{count}}개",
"status_running": "작업 중",
"status_queued": "대기 중",
"chip_label": "작업 중",

View File

@@ -287,6 +287,7 @@
},
"agent_activity": {
"hover_header_other": "{{count}} 个智能体正在工作",
"hover_header_queued_other": "{{count}} 个智能体排队中",
"status_running": "正在工作",
"status_queued": "排队中",
"chip_label": "工作中",