mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-13 13:18:56 +02:00
* feat(squads): show member working status on squad detail page
Add a new GET /api/squads/{id}/members/status endpoint that returns each
member's derived working/idle/offline/unstable status, the issues each
agent is currently running, and the last observed activity timestamp.
The Squad detail page's Members tab consumes this snapshot to render a
status pill and an active-issue link next to each agent, with live
refresh wired through the existing task/agent/daemon WS events.
Human members are returned with status=null so the UI can keep them in
the same list without implying a presence signal. Archived agents stay
in the response and surface as offline rather than being filtered out.
Co-authored-by: multica-agent <github@multica.ai>
* fix(squads): address review feedback on member status endpoint
- i18n the "blocked" issue-status pill in squad members tab (was a
bare literal that failed `i18next/no-literal-string` lint).
- Treat any dispatched/running task as working, even when its
`agent_task_queue.issue_id` is NULL (chat / quick-create tasks).
The agent slot is occupied regardless of whether we can render an
issue link.
- Force `offline` for archived agents so they appear in the list
but never look like they're still on duty, matching the RFC
decision in MUL-2319.
- Include `workspaceKeys.squads` in the post-reconnect /
workspace-switch bulk invalidation so members-status recovers
after a disconnect during which task/runtime events were missed.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
109 lines
3.7 KiB
SQL
109 lines
3.7 KiB
SQL
-- name: CreateSquad :one
|
||
INSERT INTO squad (workspace_id, name, description, leader_id, creator_id, avatar_url)
|
||
VALUES ($1, $2, $3, $4, $5, $6)
|
||
RETURNING *;
|
||
|
||
-- name: GetSquad :one
|
||
SELECT * FROM squad WHERE id = $1;
|
||
|
||
-- name: GetSquadInWorkspace :one
|
||
SELECT * FROM squad WHERE id = $1 AND workspace_id = $2;
|
||
|
||
-- name: ListSquads :many
|
||
SELECT * FROM squad WHERE workspace_id = $1 AND archived_at IS NULL ORDER BY created_at ASC;
|
||
|
||
-- name: ListAllSquads :many
|
||
SELECT * FROM squad WHERE workspace_id = $1 ORDER BY created_at ASC;
|
||
|
||
-- name: UpdateSquad :one
|
||
UPDATE squad SET
|
||
name = COALESCE(sqlc.narg('name'), name),
|
||
description = COALESCE(sqlc.narg('description'), description),
|
||
leader_id = COALESCE(sqlc.narg('leader_id'), leader_id),
|
||
avatar_url = COALESCE(sqlc.narg('avatar_url'), avatar_url),
|
||
instructions = COALESCE(sqlc.narg('instructions'), instructions),
|
||
updated_at = now()
|
||
WHERE id = $1
|
||
RETURNING *;
|
||
|
||
-- name: ArchiveSquad :one
|
||
UPDATE squad SET archived_at = now(), archived_by = $2, updated_at = now()
|
||
WHERE id = $1
|
||
RETURNING *;
|
||
|
||
-- name: AddSquadMember :one
|
||
INSERT INTO squad_member (squad_id, member_type, member_id, role)
|
||
VALUES ($1, $2, $3, $4)
|
||
RETURNING *;
|
||
|
||
-- name: RemoveSquadMember :execrows
|
||
DELETE FROM squad_member
|
||
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3;
|
||
|
||
-- name: ListSquadMembers :many
|
||
SELECT * FROM squad_member WHERE squad_id = $1 ORDER BY created_at ASC;
|
||
|
||
-- name: UpdateSquadMemberRole :one
|
||
UPDATE squad_member SET role = $4
|
||
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
|
||
RETURNING *;
|
||
|
||
-- name: IsSquadMember :one
|
||
SELECT EXISTS(
|
||
SELECT 1 FROM squad_member
|
||
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
|
||
) AS is_member;
|
||
|
||
-- name: CountSquadMembers :one
|
||
SELECT count(*) FROM squad_member WHERE squad_id = $1;
|
||
|
||
-- name: GetSquadByAssignee :one
|
||
-- Look up the squad when an issue is assigned to a squad.
|
||
SELECT s.* FROM squad s WHERE s.id = $1 AND s.workspace_id = $2;
|
||
|
||
-- name: ListSquadsByMember :many
|
||
-- Find all squads a given entity belongs to in a workspace.
|
||
SELECT s.* FROM squad s
|
||
JOIN squad_member sm ON sm.squad_id = s.id
|
||
WHERE s.workspace_id = $1 AND sm.member_type = $2 AND sm.member_id = $3
|
||
ORDER BY s.created_at ASC;
|
||
|
||
-- name: TransferSquadAssignees :exec
|
||
-- Transfer all issues assigned to a squad to the squad's leader agent.
|
||
UPDATE issue SET assignee_type = 'agent', assignee_id = $2, updated_at = now()
|
||
WHERE assignee_type = 'squad' AND assignee_id = $1;
|
||
|
||
-- name: ListSquadMemberStatusRows :many
|
||
-- Per-row join used to build the squad-members status view. One row per
|
||
-- (squad_member × active_task); members with no active task return a
|
||
-- single row with NULL task_* columns. Human members and agent members
|
||
-- with no agent row also return one row with NULL agent_/runtime_ columns.
|
||
-- The handler aggregates rows by member_id.
|
||
SELECT
|
||
sm.id AS squad_member_id,
|
||
sm.member_type AS member_type,
|
||
sm.member_id AS member_id,
|
||
a.archived_at AS agent_archived_at,
|
||
ar.status AS runtime_status,
|
||
ar.last_seen_at AS runtime_last_seen_at,
|
||
atq.id AS task_id,
|
||
atq.status AS task_status,
|
||
atq.issue_id AS task_issue_id,
|
||
atq.dispatched_at AS task_dispatched_at,
|
||
i.number AS issue_number,
|
||
i.title AS issue_title,
|
||
i.status AS issue_status
|
||
FROM squad_member sm
|
||
LEFT JOIN agent a
|
||
ON sm.member_type = 'agent' AND a.id = sm.member_id
|
||
LEFT JOIN agent_runtime ar
|
||
ON ar.id = a.runtime_id
|
||
LEFT JOIN agent_task_queue atq
|
||
ON sm.member_type = 'agent'
|
||
AND atq.agent_id = sm.member_id
|
||
AND atq.status IN ('dispatched', 'running')
|
||
LEFT JOIN issue i
|
||
ON i.id = atq.issue_id
|
||
WHERE sm.squad_id = $1
|
||
ORDER BY sm.created_at ASC, atq.dispatched_at DESC NULLS LAST;
|