mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 12:16:54 +00:00
feat(teams): add limit param to ListTasks + lightweight get-light endpoint
- Add limit parameter to ListTasks interface (dashboard=200, agent=30) - Add teams.tasks.get-light WS method returning task only (no comments/events) - Truncate dashboard response to exact limit (fix off-by-one from limit+1) - Update all 7 ListTasks callers with explicit limit values
This commit is contained in:
1 parent
b735c16d93
commit
b231878a85
8 files changed
+73
-15
No files matched your search
@@ -763,7 +763,7 @@ func buildTaskBoardSnapshot(ctx context.Context, teamStore store.TeamStore, team
|
||||
if team, err := teamStore.GetTeam(ctx, teamID); err == nil && tools.IsSharedWorkspace(team.Settings) {
|
||||
snapshotChatID = ""
|
||||
}
|
||||
allTasks, err := teamStore.ListTasks(ctx, teamID, "", store.TeamTaskFilterAll, "", "", snapshotChatID, 0)
|
||||
allTasks, err := teamStore.ListTasks(ctx, teamID, "", store.TeamTaskFilterAll, "", "", snapshotChatID, 0, 0)
|
||||
if err != nil || len(allTasks) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
// Stale recovery (expired lock → pending) is handled by the background TaskTicker.
|
||||
if l.teamStore != nil && l.agentUUID != uuid.Nil {
|
||||
if team, _ := l.teamStore.GetTeamForAgent(ctx, l.agentUUID); team != nil && team.LeadAgentID == l.agentUUID {
|
||||
if tasks, err := l.teamStore.ListTasks(ctx, team.ID, "newest", "active", req.UserID, "", "", 0); err == nil {
|
||||
if tasks, err := l.teamStore.ListTasks(ctx, team.ID, "newest", "active", req.UserID, "", "", 0, 0); err == nil {
|
||||
var stale []string
|
||||
var inProgress []string
|
||||
for _, t := range tasks {
|
||||
@@ -465,7 +465,7 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
var skillNudge70Sent, skillNudge90Sent bool
|
||||
var skillPostscriptSent bool
|
||||
|
||||
// Member progress nudge: remind dispatched members to report progress (every 10 iterations).
|
||||
// Member progress nudge: remind dispatched members to report progress (every 6 iterations).
|
||||
|
||||
// Inject retry hook so channels can update placeholder on LLM retries.
|
||||
ctx = providers.WithRetryHook(ctx, func(attempt, maxAttempts int, err error) {
|
||||
@@ -524,9 +524,9 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Member progress nudge: remind to report progress every 10 iterations.
|
||||
// Member progress nudge: remind to report progress every 6 iterations.
|
||||
// Suggests percent based on iteration ratio — model can adjust but has a baseline.
|
||||
if req.TeamTaskID != "" && memberTaskSubject != "" && iteration > 0 && iteration%10 == 0 {
|
||||
if req.TeamTaskID != "" && memberTaskSubject != "" && iteration > 0 && iteration%6 == 0 {
|
||||
var nudge string
|
||||
if maxIter > 0 {
|
||||
suggestedPct := iteration * 100 / maxIter
|
||||
|
||||
@@ -81,7 +81,7 @@ func (c *Channel) handleTasksList(ctx context.Context, chatID int64, isGroup boo
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup), "", "", 0)
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup), "", "", 0, 0)
|
||||
if err != nil {
|
||||
slog.Warn("tasks command: ListTasks failed", "error", err)
|
||||
send("Failed to list tasks. Please try again.")
|
||||
@@ -173,7 +173,7 @@ func (c *Channel) handleTaskDetail(ctx context.Context, chatID int64, text strin
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup), "", "", 0)
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup), "", "", 0, 0)
|
||||
if err != nil {
|
||||
slog.Warn("task_detail command: ListTasks failed", "error", err)
|
||||
send("Failed to list tasks. Please try again.")
|
||||
@@ -236,7 +236,7 @@ func (c *Channel) handleCallbackQuery(ctx context.Context, query *telego.Callbac
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup), "", "", 0)
|
||||
tasks, err := c.teamStore.ListTasks(ctx, team.ID, "newest", store.TeamTaskFilterAll, taskUserID(c.Name(), chatID, isGroup), "", "", 0, 0)
|
||||
if err != nil {
|
||||
send("Failed to list tasks.")
|
||||
return
|
||||
|
||||
@@ -156,13 +156,20 @@ func (m *TeamsMethods) handleTaskList(ctx context.Context, client *gateway.Clien
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := m.teamStore.ListTasks(ctx, teamID, "newest", params.Status, "", params.Channel, params.ChatID, 0)
|
||||
const dashboardLimit = 200
|
||||
tasks, err := m.teamStore.ListTasks(ctx, teamID, "newest", params.Status, "", params.Channel, params.ChatID, dashboardLimit, 0)
|
||||
if err != nil {
|
||||
slog.Warn("teams.tasks.list failed", "team_id", teamID, "status_filter", params.Status, "error", err)
|
||||
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// ListTasks returns limit+1 rows for hasMore detection; truncate for dashboard response.
|
||||
hasMore := len(tasks) > dashboardLimit
|
||||
if hasMore {
|
||||
tasks = tasks[:dashboardLimit]
|
||||
}
|
||||
|
||||
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
|
||||
"tasks": tasks,
|
||||
"count": len(tasks),
|
||||
|
||||
@@ -46,6 +46,7 @@ func (m *TeamsMethods) parseTaskParams(ctx context.Context, client *gateway.Clie
|
||||
// RegisterTasks registers teams.tasks.* RPC handlers.
|
||||
func (m *TeamsMethods) RegisterTasks(router *gateway.MethodRouter) {
|
||||
router.Register(protocol.MethodTeamsTaskGet, m.handleTaskGet)
|
||||
router.Register(protocol.MethodTeamsTaskGetLight, m.handleTaskGetLight)
|
||||
router.Register(protocol.MethodTeamsTaskApprove, m.handleTaskApprove)
|
||||
router.Register(protocol.MethodTeamsTaskReject, m.handleTaskReject)
|
||||
router.Register(protocol.MethodTeamsTaskComment, m.handleTaskComment)
|
||||
@@ -111,6 +112,47 @@ func (m *TeamsMethods) handleTaskGet(ctx context.Context, client *gateway.Client
|
||||
}))
|
||||
}
|
||||
|
||||
// --- Task Get Light (task only, no comments/events/attachments) ---
|
||||
|
||||
func (m *TeamsMethods) handleTaskGetLight(ctx context.Context, client *gateway.Client, req *protocol.RequestFrame) {
|
||||
var params taskGetParams
|
||||
locale, ok := m.parseTaskParams(ctx, client, req, ¶ms)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
teamID, err := uuid.Parse(params.TeamID)
|
||||
if err != nil {
|
||||
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidID, "teamId")))
|
||||
return
|
||||
}
|
||||
taskID, err := uuid.Parse(params.TaskID)
|
||||
if err != nil {
|
||||
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidID, "taskId")))
|
||||
return
|
||||
}
|
||||
|
||||
task, err := m.teamStore.GetTask(ctx, taskID)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrTaskNotFound) {
|
||||
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrNotFound, i18n.T(locale, i18n.MsgNotFound, "task", "")))
|
||||
} else {
|
||||
slog.Warn("teams.tasks.get-light failed", "task_id", taskID, "error", err)
|
||||
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, i18n.T(locale, i18n.MsgInternalError, "")))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if task.TeamID != teamID {
|
||||
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrNotFound, i18n.T(locale, i18n.MsgNotFound, "task", "")))
|
||||
return
|
||||
}
|
||||
|
||||
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
|
||||
"task": task,
|
||||
}))
|
||||
}
|
||||
|
||||
// --- Task Approve ---
|
||||
|
||||
type taskApproveParams struct {
|
||||
@@ -487,7 +529,8 @@ func (m *TeamsMethods) handleTaskCreate(ctx context.Context, client *gateway.Cli
|
||||
TaskID: task.ID.String(),
|
||||
Status: store.TeamTaskStatusPending,
|
||||
UserID: client.UserID(),
|
||||
Channel: "dashboard",
|
||||
Channel: ch,
|
||||
ChatID: cid,
|
||||
Timestamp: taskNowUTC(),
|
||||
ActorType: "human",
|
||||
ActorID: client.UserID(),
|
||||
@@ -500,7 +543,8 @@ func (m *TeamsMethods) handleTaskCreate(ctx context.Context, client *gateway.Cli
|
||||
Status: store.TeamTaskStatusInProgress,
|
||||
OwnerAgentKey: autoAssignedAgentID.String(),
|
||||
UserID: client.UserID(),
|
||||
Channel: "dashboard",
|
||||
Channel: ch,
|
||||
ChatID: cid,
|
||||
Timestamp: taskNowUTC(),
|
||||
ActorType: "human",
|
||||
ActorID: client.UserID(),
|
||||
@@ -573,7 +617,8 @@ func (m *TeamsMethods) handleTaskAssign(ctx context.Context, client *gateway.Cli
|
||||
TaskID: taskID.String(),
|
||||
Status: store.TeamTaskStatusInProgress,
|
||||
UserID: client.UserID(),
|
||||
Channel: "dashboard",
|
||||
Channel: task.Channel,
|
||||
ChatID: task.ChatID,
|
||||
Timestamp: taskNowUTC(),
|
||||
ActorType: "human",
|
||||
ActorID: client.UserID(),
|
||||
|
||||
@@ -163,7 +163,7 @@ func (s *PGTeamStore) UpdateTask(ctx context.Context, taskID uuid.UUID, updates
|
||||
return execMapUpdate(ctx, s.db, "team_tasks", taskID, updates)
|
||||
}
|
||||
|
||||
func (s *PGTeamStore) ListTasks(ctx context.Context, teamID uuid.UUID, orderBy string, statusFilter string, userID string, channel string, chatID string, offset int) ([]store.TeamTaskData, error) {
|
||||
func (s *PGTeamStore) ListTasks(ctx context.Context, teamID uuid.UUID, orderBy string, statusFilter string, userID string, channel string, chatID string, limit int, offset int) ([]store.TeamTaskData, error) {
|
||||
orderClause := "t.priority DESC, t.created_at"
|
||||
if orderBy == "newest" {
|
||||
orderClause = "t.created_at DESC"
|
||||
@@ -180,6 +180,10 @@ func (s *PGTeamStore) ListTasks(ctx context.Context, teamID uuid.UUID, orderBy s
|
||||
// "", store.TeamTaskFilterAll ("all") → no filter (all statuses)
|
||||
}
|
||||
|
||||
if limit <= 0 {
|
||||
limit = maxListTasksRows
|
||||
}
|
||||
|
||||
// Scope filter: always bind $4/$5 but only enforce when non-empty.
|
||||
scopeWhere := "AND ($4 = '' OR COALESCE(t.channel,'') = $4) AND ($5 = '' OR COALESCE(t.chat_id,'') = $5)"
|
||||
|
||||
@@ -188,7 +192,7 @@ func (s *PGTeamStore) ListTasks(ctx context.Context, teamID uuid.UUID, orderBy s
|
||||
`+taskJoinClause+`
|
||||
WHERE t.team_id = $1 AND ($2 = '' OR t.user_id = $2) `+statusWhere+` `+scopeWhere+`
|
||||
ORDER BY `+orderClause+`
|
||||
LIMIT $3 OFFSET $6`, teamID, userID, maxListTasksRows+1, channel, chatID, offset)
|
||||
LIMIT $3 OFFSET $6`, teamID, userID, limit+1, channel, chatID, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -316,7 +316,8 @@ type TeamStore interface {
|
||||
// statusFilter: "" = non-completed (default), "completed", "all".
|
||||
// userID: if non-empty, filter to tasks created by this user.
|
||||
// channel+chatID: if either is non-empty, filter to that exact scope.
|
||||
ListTasks(ctx context.Context, teamID uuid.UUID, orderBy string, statusFilter string, userID string, channel string, chatID string, offset int) ([]TeamTaskData, error)
|
||||
// limit: max rows to return; 0 uses the default (30).
|
||||
ListTasks(ctx context.Context, teamID uuid.UUID, orderBy string, statusFilter string, userID string, channel string, chatID string, limit int, offset int) ([]TeamTaskData, error)
|
||||
// GetTask returns a single task by ID with joined agent info.
|
||||
GetTask(ctx context.Context, taskID uuid.UUID) (*TeamTaskData, error)
|
||||
// GetTasksByIDs returns multiple tasks by IDs in a single query.
|
||||
|
||||
@@ -108,6 +108,7 @@ const (
|
||||
MethodTeamsDelete = "teams.delete"
|
||||
MethodTeamsTaskList = "teams.tasks.list"
|
||||
MethodTeamsTaskGet = "teams.tasks.get"
|
||||
MethodTeamsTaskGetLight = "teams.tasks.get-light"
|
||||
MethodTeamsTaskApprove = "teams.tasks.approve"
|
||||
MethodTeamsTaskReject = "teams.tasks.reject"
|
||||
MethodTeamsTaskComment = "teams.tasks.comment"
|
||||
|
||||
Reference in new issue
Block a user