Files
goclaw/internal/tools/team_tasks_tool.go
T
Viet TranandGitHub 84650c5c14 feat(teams): attachments refactor, semantic search, improved prompting (#310)
* feat(teams): refactor attachments, remove team_message, add task comments UI

Major team system refactoring:

- Drop team_workspace_files, team_workspace_file_versions, team_workspace_comments,
  team_messages tables; replace team_task_attachments with path-based schema
- Add denormalized comment_count/attachment_count on team_tasks for dashboard perf
- Auto-track file writes as task attachments via WorkspaceInterceptor
- Remove team_message tool entirely (tool, store, i18n, builtin_tools, MCP bridge)
- Members communicate via task comments; approve/reject use comments for audit trail
- Add commented/new_task notification types to TeamNotifyConfig
- Enrich task completion announce with member comments
- User-created tasks stay pending (backlog) — no auto-assign to leader
- Configurable member request tasks (member_requests.enabled in team settings)
- Structured task description template in TEAM.md for v2 leads
- HTTP attachment download endpoint with IDOR + path traversal protection
- Web UI: count badges on task list, comments section with input, download button
- Team settings UI: completed/commented/new_task toggles, member requests section

* feat(teams): priority dispatch, compact prompting, realtime comments

- Priority dispatch: DispatchUnblockedTasks dispatches only 1 task per
  owner per round (highest priority first). Fixes cancel bug where
  CancelSession killed innocent queued tasks.
- Prompt rework: Replace verbose Task Decomposition (25 lines) with
  compact Task Planning (8 lines). Add explicit UUID warning and
  sequencing guidance for weak models (Qwen, MiniMax).
- Recent comments in dispatch: buildRecentCommentsSummary appends 3
  most recent comments to re-dispatched tasks (reject, retry, stale).
- Enrich comment event payload with TaskNumber, Subject, CommentText
  (truncated 500 runes, UTF-8 safe).
- UI: Board subscribes to TEAM_TASK_COMMENTED for realtime comment_count
  badge updates. Task detail dialog auto-refreshes comments on event.
- Tool description hint: guide models to write self-contained task
  descriptions with clear objectives and context.

* perf(teams): add ListRecentTaskComments with SQL LIMIT

Dispatch only needs 3 most recent comments — avoid fetching all.
New ListRecentTaskComments(ctx, taskID, limit) uses ORDER BY DESC
LIMIT N then reverses to chronological order.

* feat(teams): add subject embedding for semantic task search + improve prompting

- Add vector(1536) embedding column to team_tasks with HNSW index
- Implement hybrid search: FTS (0.3) + cosine similarity (0.7) with graceful fallback
- Auto-generate embeddings on task create/update, backfill existing tasks on startup
- Wire embedding provider into PGTeamStore via gateway_setup
- Change FTS from OR to AND with prefix matching for precise keyword search
- Reduce search page size from 30 to 5 to save tokens
- Rename migration 000023 → 000024, bump RequiredSchemaVersion to 24
- Update TEAM.md hints: prefer search over list, batch task creation with blocked_by
- Add anti-pattern examples to prevent sequential task creation
2026-03-20 17:51:40 +07:00

157 lines
5.7 KiB
Go

package tools
import (
"context"
"fmt"
)
// TeamTasksTool exposes the shared team task list to agents.
// Actions: list, get, create, claim, complete, cancel, search, review, comment, progress, attach, update.
type TeamTasksTool struct {
manager *TeamToolManager
}
func NewTeamTasksTool(manager *TeamToolManager) *TeamTasksTool {
return &TeamTasksTool{manager: manager}
}
func (t *TeamTasksTool) Name() string { return "team_tasks" }
func (t *TeamTasksTool) Description() string {
return "Manage the shared team task list (create, claim, complete, track progress). See TEAM.md for available actions and team context."
}
func (t *TeamTasksTool) Parameters() map[string]any {
return map[string]any{
"type": "object",
"properties": map[string]any{
"action": map[string]any{
"type": "string",
"description": "'list', 'get', 'create', 'claim', 'complete', 'cancel', 'approve', 'reject', 'search', 'review', 'comment', 'progress', 'attach', 'update', 'ask_user', or 'clear_ask_user'. " +
"ask_user: set a periodic reminder that is sent to the USER (not the team) when you need the user's input/decision to continue (e.g. 'Which design do you prefer?'). ONLY use when you have a question for the user. Do NOT use for status updates, waiting for teammates, or notifications — use 'progress' instead. " +
"clear_ask_user: cancel a previously set ask_user reminder. " +
"retry: re-dispatch a stale or failed task.",
},
"task_id": map[string]any{
"type": "string",
"description": "Task UUID (required for most actions except list, create, search). When working on a dispatched task, this is auto-resolved from context — you can omit it for complete/progress/comment.",
},
"subject": map[string]any{
"type": "string",
"description": "Task subject (required for create, optional for update)",
},
"description": map[string]any{
"type": "string",
"description": "Task description with clear objectives and requirements. Include all context the assignee needs to complete the task independently.",
},
"result": map[string]any{
"type": "string",
"description": "Result summary (required for complete)",
},
"text": map[string]any{
"type": "string",
"description": "Text content: comment text, cancel/reject reason, progress update, or ask_user reminder question (must be a question asking the user for input/decision)",
},
"status": map[string]any{
"type": "string",
"description": "Filter for list: '' (all, default), 'active', 'completed', 'in_review'",
},
"query": map[string]any{
"type": "string",
"description": "Search query for action=search (supports keyword AND semantic matching). Use search before create to check for duplicates.",
},
"priority": map[string]any{
"type": "number",
"description": "Priority, higher = more important (for create, default 0)",
},
"blocked_by": map[string]any{
"type": "array",
"items": map[string]any{"type": "string"},
"description": "Task IDs that must complete first (for create/update)",
},
"require_approval": map[string]any{
"type": "boolean",
"description": "Require user approval before claim (for create, default false)",
},
"percent": map[string]any{
"type": "integer",
"description": "Progress percentage 0-100 (for progress action)",
},
"file_id": map[string]any{
"type": "string",
"description": "Workspace file ID (for attach)",
},
"assignee": map[string]any{
"type": "string",
"description": "Agent key to assign task to (REQUIRED for create). Auto-dispatches to that team member.",
},
"page": map[string]any{
"type": "number",
"description": "Page number for list/search (default 1, 30 per page)",
},
},
"required": []string{"action"},
}
}
// v2Actions lists team_tasks actions that require team version >= 2.
var v2Actions = map[string]bool{
"approve": true, "reject": true, "review": true, "comment": true,
"progress": true, "attach": true, "update": true,
"ask_user": true, "clear_ask_user": true, "retry": true,
}
func (t *TeamTasksTool) Execute(ctx context.Context, args map[string]any) *Result {
action, _ := args["action"].(string)
// Gate v2-only actions: resolve team once and check version.
if v2Actions[action] {
team, _, err := t.manager.resolveTeam(ctx)
if err != nil {
return ErrorResult(err.Error())
}
if !IsTeamV2(team) {
return ErrorResult(fmt.Sprintf("action '%s' requires team version 2 — upgrade in team settings", action))
}
}
switch action {
case "list":
return t.executeList(ctx, args)
case "get":
return t.executeGet(ctx, args)
case "create":
return t.executeCreate(ctx, args)
case "claim":
return t.executeClaim(ctx, args)
case "complete":
return t.executeComplete(ctx, args)
case "cancel":
return t.executeCancel(ctx, args)
case "approve":
return t.executeApprove(ctx, args)
case "reject":
return t.executeReject(ctx, args)
case "search":
return t.executeSearch(ctx, args)
case "review":
return t.executeReview(ctx, args)
case "comment":
return t.executeComment(ctx, args)
case "progress":
return t.executeProgress(ctx, args)
case "attach":
return t.executeAttach(ctx, args)
case "update":
return t.executeUpdate(ctx, args)
case "ask_user":
return t.executeAskUser(ctx, args)
case "clear_ask_user":
return t.executeClearAskUser(ctx, args)
case "retry":
return t.executeRetry(ctx, args)
default:
return ErrorResult(fmt.Sprintf("unknown action: %s (use list, get, create, claim, complete, cancel, search, review, comment, progress, attach, update, ask_user, clear_ask_user, or retry)", action))
}
}