Files
multica/server/pkg/protocol/events.go
Naiyuan Qing fedd0f1694 feat(issues): live agent activity chip + per-issue indicator + filter (#3058)
* feat(server): broadcast task:running event

The dispatched → running transition was silent: only task:queued,
task:dispatch, task:cancelled, task:completed and task:failed
broadcast over WS. Any UI that distinguishes "queued" from "running"
(e.g. the new issue-card agent activity indicator) would lag by up to
the 30s agentTaskSnapshot staleTime on the most user-visible
transition. StartTask now broadcasts task:running so the workspace
snapshot invalidates immediately, keeping the agent activity UI live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(issues): live agent activity chip + per-issue indicator + filter

Surfaces "which agents are working on what, right now" in the Issues
and My Issues views, with a one-click filter to narrow the list to
issues that have a running agent task.

Two visual surfaces:

- **Workspace chip** in the header (left of Filter). Shows the
  brand-tinted avatar stack of agents currently running on visible
  issues. Click toggles a page-scoped filter; idle state renders a
  static "0 working" button with a hover-card placeholder. When the
  filter is active the chip pins to brand fill across hover and popover
  states (the Button outline variant otherwise repaints back to
  neutral). A muted "Viewing only working agents" hint sits to the
  left of the chip whenever the filter is on, so users notice the
  active state without having to hover.

- **Per-issue indicator** on every board card and list row (top-right
  of the identifier line). Renders the avatar stack of agents in
  running or queued state on that issue, full-opacity ring at brand/70
  when ≥1 is running, half-opacity stack when only queued. Returns
  null when nothing is in flight.

Both surfaces open the same hover-card body that lists each active
task with the agent avatar, status dot (composed via the existing
availability + workload tokens), and a live-ticking duration.

Adds a new "All" scope to /my-issues that unions assignee, creator,
and involves_user_id via three parallel fetches deduped on the
client — no backend changes for this part. The chip's count and the
quick-filter both use the page's currently visible issue ids so they
stay in sync with the active scope.

State is per-user (Zustand + localStorage) and the agentRunningFilter
is intentionally omitted from partialize — running state changes
second-to-second and a stored toggle would land users in an
unexplained empty list. WS task:running, already added in the
preceding commit, drives real-time updates without polling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(issues): swap indicator ring pulse for shimmer text label

Earlier iterations layered a brand ring with various opacity-pulse
cadences around the per-issue avatar stack. Every tuning attempt was
either invisible (transparent ring + faded pulse) or oppressive (a
visible ring that flashed on a dense board). Moves the "alive" signal
onto a small text label and reuses chat's existing
`animate-chat-text-shimmer` utility — a soft light sweep across the
glyphs that already powers the ChatGPT-style "thinking" cue in
task-status-pill.

Indicator now reads as a 12 px avatar stack + 10 px label:

- Running → full-opacity avatars + shimmering localized "Working"
- Queued  → half-opacity avatars + muted static "Queued"
- Idle    → render nothing (unchanged)

Avatars and the surrounding card stay completely still; only the few
glyphs animate. The label is i18n-driven via the existing
`status_running` / `status_queued` keys, so no locale changes are
required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:20:42 +08:00

126 lines
4.4 KiB
Go

package protocol
// Event types for WebSocket communication between server, web clients, and daemon.
const (
// Issue events
EventIssueCreated = "issue:created"
EventIssueUpdated = "issue:updated"
EventIssueDeleted = "issue:deleted"
EventIssueMetadataChanged = "issue_metadata:changed"
// Comment events
EventCommentCreated = "comment:created"
EventCommentUpdated = "comment:updated"
EventCommentDeleted = "comment:deleted"
EventCommentResolved = "comment:resolved"
EventCommentUnresolved = "comment:unresolved"
EventReactionAdded = "reaction:added"
EventReactionRemoved = "reaction:removed"
EventIssueReactionAdded = "issue_reaction:added"
EventIssueReactionRemoved = "issue_reaction:removed"
// Agent events
EventAgentStatus = "agent:status"
EventAgentCreated = "agent:created"
EventAgentArchived = "agent:archived"
EventAgentRestored = "agent:restored"
// Task events (server <-> daemon).
// Each event maps to a status transition on agent_task_queue. Front-end
// subscribes by `task:` prefix and invalidates the workspace task
// snapshot, so the granularity here is "what does the user want to see
// change" — not "every internal status flip".
EventTaskQueued = "task:queued" // ∅ → queued (enqueue / retry create)
EventTaskDispatch = "task:dispatch" // queued → dispatched (daemon claim)
EventTaskRunning = "task:running" // dispatched → running (daemon started)
EventTaskProgress = "task:progress"
EventTaskCompleted = "task:completed" // running → completed
EventTaskFailed = "task:failed" // running → failed
EventTaskMessage = "task:message"
EventTaskCancelled = "task:cancelled" // * → cancelled
// Inbox events
EventInboxNew = "inbox:new"
EventInboxRead = "inbox:read"
EventInboxArchived = "inbox:archived"
EventInboxBatchRead = "inbox:batch-read"
EventInboxBatchArchived = "inbox:batch-archived"
// Workspace events
EventWorkspaceUpdated = "workspace:updated"
EventWorkspaceDeleted = "workspace:deleted"
// Member events
EventMemberAdded = "member:added"
EventMemberUpdated = "member:updated"
EventMemberRemoved = "member:removed"
// Subscriber events
EventSubscriberAdded = "subscriber:added"
EventSubscriberRemoved = "subscriber:removed"
// Activity events
EventActivityCreated = "activity:created"
// Skill events
EventSkillCreated = "skill:created"
EventSkillUpdated = "skill:updated"
EventSkillDeleted = "skill:deleted"
// Chat events
EventChatMessage = "chat:message"
EventChatDone = "chat:done"
EventChatSessionRead = "chat:session_read"
EventChatSessionDeleted = "chat:session_deleted"
EventChatSessionUpdated = "chat:session_updated"
// Project events
EventProjectCreated = "project:created"
EventProjectUpdated = "project:updated"
EventProjectDeleted = "project:deleted"
EventProjectResourceCreated = "project_resource:created"
EventProjectResourceDeleted = "project_resource:deleted"
// Label events
EventLabelCreated = "label:created"
EventLabelUpdated = "label:updated"
EventLabelDeleted = "label:deleted"
EventIssueLabelsChanged = "issue_labels:changed"
// Pin events
EventPinCreated = "pin:created"
EventPinDeleted = "pin:deleted"
EventPinReordered = "pin:reordered"
// Invitation events
EventInvitationCreated = "invitation:created"
EventInvitationAccepted = "invitation:accepted"
EventInvitationDeclined = "invitation:declined"
EventInvitationRevoked = "invitation:revoked"
// Autopilot events
EventAutopilotCreated = "autopilot:created"
EventAutopilotUpdated = "autopilot:updated"
EventAutopilotDeleted = "autopilot:deleted"
EventAutopilotRunStart = "autopilot:run_start"
EventAutopilotRunDone = "autopilot:run_done"
// Squad events
EventSquadCreated = "squad:created"
EventSquadUpdated = "squad:updated"
EventSquadDeleted = "squad:deleted"
// Daemon events
EventDaemonHeartbeat = "daemon:heartbeat"
EventDaemonHeartbeatAck = "daemon:heartbeat_ack"
EventDaemonRegister = "daemon:register"
EventDaemonTaskAvailable = "daemon:task_available"
// GitHub integration events
EventGitHubInstallationCreated = "github_installation:created"
EventGitHubInstallationDeleted = "github_installation:deleted"
EventPullRequestLinked = "pull_request:linked"
EventPullRequestUpdated = "pull_request:updated"
EventPullRequestUnlinked = "pull_request:unlinked"
)