diff --git a/apps/web/app/(dashboard)/agents/page.tsx b/apps/web/app/(dashboard)/agents/page.tsx index d28099be..10b184ec 100644 --- a/apps/web/app/(dashboard)/agents/page.tsx +++ b/apps/web/app/(dashboard)/agents/page.tsx @@ -8,15 +8,10 @@ import { Monitor, Plus, ListTodo, - Wrench, FileText, BookOpenText, - MessageSquare, - Timer, Trash2, Save, - Key, - Link2, Clock, CheckCircle2, XCircle, @@ -35,9 +30,6 @@ import type { Agent, AgentStatus, AgentVisibility, - AgentTool, - AgentTrigger, - AgentTriggerType, AgentTask, RuntimeDevice, CreateAgentRequest, @@ -151,10 +143,6 @@ function CreateAgentDialog({ description: description.trim(), runtime_id: selectedRuntime.id, visibility, - triggers: [ - { id: generateId(), type: "on_assign", enabled: true, config: {} }, - { id: generateId(), type: "on_comment", enabled: true, config: {} }, - ], }); onClose(); } catch (err) { @@ -600,466 +588,6 @@ function SkillsTab({ ); } -// --------------------------------------------------------------------------- -// Tools Tab -// --------------------------------------------------------------------------- - -function AddToolDialog({ - onClose, - onAdd, -}: { - onClose: () => void; - onAdd: (tool: AgentTool) => void; -}) { - const [name, setName] = useState(""); - const [description, setDescription] = useState(""); - const [authType, setAuthType] = useState<"oauth" | "api_key" | "none">("api_key"); - - const handleAdd = () => { - if (!name.trim()) return; - onAdd({ - id: generateId(), - name: name.trim(), - description: description.trim(), - auth_type: authType, - connected: false, - config: {}, - }); - onClose(); - }; - - return ( - { if (!v) onClose(); }}> - - - Add Tool - - Connect an external tool for this agent to use. - - - - - - Tool Name - setName(e.target.value)} - placeholder="e.g. Google Search, Slack, GitHub" - className="mt-1" - onKeyDown={(e) => e.key === "Enter" && handleAdd()} - /> - - - Description - setDescription(e.target.value)} - placeholder="What does this tool do?" - className="mt-1" - /> - - - Authentication - - {(["api_key", "oauth", "none"] as const).map((type) => ( - setAuthType(type)} - className={`flex-1 ${ - authType === type - ? "border-primary bg-primary/5 font-medium" - : "" - }`} - > - {type === "api_key" ? "API Key" : type === "oauth" ? "OAuth" : "None"} - - ))} - - - - - - - Cancel - - - Add - - - - - ); -} - -function ToolsTab({ - agent, - onSave, -}: { - agent: Agent; - onSave: (tools: AgentTool[]) => Promise; -}) { - const [tools, setTools] = useState(agent.tools ?? []); - const [showAdd, setShowAdd] = useState(false); - const [saving, setSaving] = useState(false); - - useEffect(() => { - setTools(agent.tools ?? []); - }, [agent.id, agent.tools]); - - const isDirty = JSON.stringify(tools) !== JSON.stringify(agent.tools ?? []); - - const handleSave = async () => { - setSaving(true); - try { - await onSave(tools); - } catch { - // toast handled by parent - } finally { - setSaving(false); - } - }; - - const toggleConnect = (toolId: string) => { - setTools((prev) => - prev.map((t) => (t.id === toolId ? { ...t, connected: !t.connected } : t)), - ); - }; - - const removeTool = (toolId: string) => { - setTools((prev) => prev.filter((t) => t.id !== toolId)); - }; - - return ( - - - - Tools - - External tools and APIs this agent can use during task execution. - - - - {isDirty && ( - - - {saving ? "Saving..." : "Save"} - - )} - setShowAdd(true)} - > - - Add Tool - - - - - {tools.length === 0 ? ( - - - No tools configured - setShowAdd(true)} - size="xs" - className="mt-3" - > - - Add Tool - - - ) : ( - - {tools.map((tool) => ( - - - {tool.auth_type === "oauth" ? ( - - ) : tool.auth_type === "api_key" ? ( - - ) : ( - - )} - - - {tool.name} - {tool.description && ( - - {tool.description} - - )} - - - toggleConnect(tool.id)} - className={ - tool.connected - ? "bg-success/10 text-success" - : "bg-muted text-muted-foreground hover:bg-accent" - } - > - {tool.connected ? "Connected" : "Connect"} - - removeTool(tool.id)} - className="text-muted-foreground hover:text-destructive" - > - - - - - ))} - - )} - - {showAdd && ( - setShowAdd(false)} - onAdd={(tool) => setTools((prev) => [...prev, tool])} - /> - )} - - ); -} - -// --------------------------------------------------------------------------- -// Triggers Tab -// --------------------------------------------------------------------------- - -function TriggersTab({ - agent, - onSave, -}: { - agent: Agent; - onSave: (triggers: AgentTrigger[]) => Promise; -}) { - const [triggers, setTriggers] = useState(agent.triggers ?? []); - const [saving, setSaving] = useState(false); - - useEffect(() => { - setTriggers(agent.triggers ?? []); - }, [agent.id, agent.triggers]); - - const isDirty = JSON.stringify(triggers) !== JSON.stringify(agent.triggers ?? []); - - const handleSave = async () => { - setSaving(true); - try { - await onSave(triggers); - } catch { - // toast handled by parent - } finally { - setSaving(false); - } - }; - - const toggleTrigger = (triggerId: string) => { - setTriggers((prev) => - prev.map((t) => (t.id === triggerId ? { ...t, enabled: !t.enabled } : t)), - ); - }; - - const removeTrigger = (triggerId: string) => { - setTriggers((prev) => prev.filter((t) => t.id !== triggerId)); - }; - - const addTrigger = (type: AgentTriggerType) => { - const newTrigger: AgentTrigger = { - id: generateId(), - type, - enabled: true, - config: type === "scheduled" ? { cron: "0 9 * * 1-5", timezone: "UTC" } : {}, - }; - setTriggers((prev) => [...prev, newTrigger]); - }; - - const updateTriggerConfig = (triggerId: string, config: Record) => { - setTriggers((prev) => - prev.map((t) => (t.id === triggerId ? { ...t, config } : t)), - ); - }; - - return ( - - - - Triggers - - Configure when this agent should start working. - - - - {isDirty && ( - - - {saving ? "Saving..." : "Save"} - - )} - - - - - {triggers.map((trigger) => { - const scheduledConfig = (trigger.config ?? {}) as { - cron?: string; - timezone?: string; - }; - - return ( - - - - {trigger.type === "on_assign" ? ( - - ) : trigger.type === "on_comment" ? ( - - ) : ( - - )} - - - - {trigger.type === "on_assign" - ? "On Issue Assign" - : trigger.type === "on_comment" - ? "On Comment" - : "Scheduled"} - - - {trigger.type === "on_assign" - ? "Runs when an issue is assigned to this agent" - : trigger.type === "on_comment" - ? "Runs when a member comments on the agent's issue" - : `Cron: ${scheduledConfig.cron ?? "Not set"}`} - - - - toggleTrigger(trigger.id)} - className={`relative h-5 w-9 rounded-full transition-colors ${ - trigger.enabled ? "bg-primary" : "bg-muted" - }`} - > - - - removeTrigger(trigger.id)} - className="text-muted-foreground hover:text-destructive" - > - - - - - - {trigger.type === "scheduled" && ( - - - - Cron Expression - - - updateTriggerConfig(trigger.id, { - ...(trigger.config ?? {}), - cron: e.target.value, - }) - } - placeholder="0 9 * * 1-5" - className="mt-1 text-xs font-mono" - /> - - - - Timezone - - - updateTriggerConfig(trigger.id, { - ...(trigger.config ?? {}), - timezone: e.target.value, - }) - } - placeholder="UTC" - className="mt-1 text-xs" - /> - - - )} - - ); - })} - - - - addTrigger("on_assign")} - className="border-dashed text-muted-foreground hover:text-foreground" - > - - Add On Assign - - addTrigger("on_comment")} - className="border-dashed text-muted-foreground hover:text-foreground" - > - - Add On Comment - - addTrigger("scheduled")} - className="border-dashed text-muted-foreground hover:text-foreground" - > - - Add Scheduled - - - - ); -} - // --------------------------------------------------------------------------- // Tasks Tab // --------------------------------------------------------------------------- @@ -1371,13 +899,11 @@ function SettingsTab({ // Agent Detail // --------------------------------------------------------------------------- -type DetailTab = "instructions" | "skills" | "tools" | "triggers" | "tasks" | "settings"; +type DetailTab = "instructions" | "skills" | "tasks" | "settings"; const detailTabs: { id: DetailTab; label: string; icon: typeof FileText }[] = [ { id: "instructions", label: "Instructions", icon: FileText }, { id: "skills", label: "Skills", icon: BookOpenText }, - { id: "tools", label: "Tools", icon: Wrench }, - { id: "triggers", label: "Triggers", icon: Timer }, { id: "tasks", label: "Tasks", icon: ListTodo }, { id: "settings", label: "Settings", icon: Settings }, ]; @@ -1491,18 +1017,6 @@ function AgentDetail({ {activeTab === "skills" && ( )} - {activeTab === "tools" && ( - onUpdate(agent.id, { tools })} - /> - )} - {activeTab === "triggers" && ( - onUpdate(agent.id, { triggers })} - /> - )} {activeTab === "tasks" && } {activeTab === "settings" && ( ; -} - -export interface AgentTrigger { - id: string; - type: AgentTriggerType; - enabled: boolean; - config: Record | null; -} - export interface AgentTask { id: string; agent_id: string; @@ -69,8 +51,6 @@ export interface Agent { max_concurrent_tasks: number; owner_id: string | null; skills: Skill[]; - tools: AgentTool[]; - triggers: AgentTrigger[]; created_at: string; updated_at: string; archived_at: string | null; @@ -86,8 +66,6 @@ export interface CreateAgentRequest { runtime_config?: Record; visibility?: AgentVisibility; max_concurrent_tasks?: number; - tools?: AgentTool[]; - triggers?: AgentTrigger[]; } export interface UpdateAgentRequest { @@ -100,8 +78,6 @@ export interface UpdateAgentRequest { visibility?: AgentVisibility; status?: AgentStatus; max_concurrent_tasks?: number; - tools?: AgentTool[]; - triggers?: AgentTrigger[]; } // Skills diff --git a/apps/web/shared/types/index.ts b/apps/web/shared/types/index.ts index 375221cf..962f255a 100644 --- a/apps/web/shared/types/index.ts +++ b/apps/web/shared/types/index.ts @@ -4,9 +4,6 @@ export type { AgentStatus, AgentRuntimeMode, AgentVisibility, - AgentTriggerType, - AgentTool, - AgentTrigger, AgentTask, AgentRuntime, RuntimeDevice, diff --git a/apps/web/test/helpers.tsx b/apps/web/test/helpers.tsx index ccbe8356..10fe30aa 100644 --- a/apps/web/test/helpers.tsx +++ b/apps/web/test/helpers.tsx @@ -58,8 +58,6 @@ export const mockAgents: Agent[] = [ max_concurrent_tasks: 3, owner_id: null, skills: [], - tools: [], - triggers: [], created_at: "2026-01-01T00:00:00Z", updated_at: "2026-01-01T00:00:00Z", archived_at: null, diff --git a/server/cmd/server/integration_test.go b/server/cmd/server/integration_test.go index 5f7120fd..5bd1549d 100644 --- a/server/cmd/server/integration_test.go +++ b/server/cmd/server/integration_test.go @@ -139,9 +139,9 @@ func setupIntegrationTestFixture(ctx context.Context, pool *pgxpool.Pool) (strin if _, err := pool.Exec(ctx, ` INSERT INTO agent ( workspace_id, name, description, runtime_mode, runtime_config, - runtime_id, visibility, max_concurrent_tasks, owner_id, tools, triggers + runtime_id, visibility, max_concurrent_tasks, owner_id ) - VALUES ($1, $2, '', 'cloud', '{}'::jsonb, $3, 'workspace', 1, $4, '[]'::jsonb, '[]'::jsonb) + VALUES ($1, $2, '', 'cloud', '{}'::jsonb, $3, 'workspace', 1, $4) `, workspaceID, "Integration Test Agent", runtimeID, userID); err != nil { return "", "", err } diff --git a/server/internal/handler/agent.go b/server/internal/handler/agent.go index bcfb3a6d..78ff89d0 100644 --- a/server/internal/handler/agent.go +++ b/server/internal/handler/agent.go @@ -28,8 +28,6 @@ type AgentResponse struct { MaxConcurrentTasks int32 `json:"max_concurrent_tasks"` OwnerID *string `json:"owner_id"` Skills []SkillResponse `json:"skills"` - Tools any `json:"tools"` - Triggers any `json:"triggers"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` ArchivedAt *string `json:"archived_at"` @@ -45,22 +43,6 @@ func agentToResponse(a db.Agent) AgentResponse { rc = map[string]any{} } - var tools any - if a.Tools != nil { - json.Unmarshal(a.Tools, &tools) - } - if tools == nil { - tools = []any{} - } - - var triggers any - if a.Triggers != nil { - json.Unmarshal(a.Triggers, &triggers) - } - if triggers == nil { - triggers = []any{} - } - return AgentResponse{ ID: uuidToString(a.ID), WorkspaceID: uuidToString(a.WorkspaceID), @@ -76,8 +58,6 @@ func agentToResponse(a db.Agent) AgentResponse { MaxConcurrentTasks: a.MaxConcurrentTasks, OwnerID: uuidToPtr(a.OwnerID), Skills: []SkillResponse{}, - Tools: tools, - Triggers: triggers, CreatedAt: timestampToString(a.CreatedAt), UpdatedAt: timestampToString(a.UpdatedAt), ArchivedAt: timestampToPtr(a.ArchivedAt), @@ -221,8 +201,6 @@ type CreateAgentRequest struct { RuntimeConfig any `json:"runtime_config"` Visibility string `json:"visibility"` MaxConcurrentTasks int32 `json:"max_concurrent_tasks"` - Tools any `json:"tools"` - Triggers any `json:"triggers"` } func (h *Handler) CreateAgent(w http.ResponseWriter, r *http.Request) { @@ -268,16 +246,6 @@ func (h *Handler) CreateAgent(w http.ResponseWriter, r *http.Request) { rc = []byte("{}") } - tools, _ := json.Marshal(req.Tools) - if req.Tools == nil { - tools = []byte("[]") - } - - triggers, _ := json.Marshal(req.Triggers) - if req.Triggers == nil { - triggers = defaultAgentTriggers() - } - agent, err := h.Queries.CreateAgent(r.Context(), db.CreateAgentParams{ WorkspaceID: parseUUID(workspaceID), Name: req.Name, @@ -290,8 +258,6 @@ func (h *Handler) CreateAgent(w http.ResponseWriter, r *http.Request) { Visibility: req.Visibility, MaxConcurrentTasks: req.MaxConcurrentTasks, OwnerID: parseUUID(ownerID), - Tools: tools, - Triggers: triggers, }) if err != nil { slog.Warn("create agent failed", append(logger.RequestAttrs(r), "error", err, "workspace_id", workspaceID)...) @@ -323,8 +289,6 @@ type UpdateAgentRequest struct { Visibility *string `json:"visibility"` Status *string `json:"status"` MaxConcurrentTasks *int32 `json:"max_concurrent_tasks"` - Tools any `json:"tools"` - Triggers any `json:"triggers"` } // canManageAgent checks whether the current user can update or archive an agent. @@ -401,14 +365,6 @@ func (h *Handler) UpdateAgent(w http.ResponseWriter, r *http.Request) { if req.MaxConcurrentTasks != nil { params.MaxConcurrentTasks = pgtype.Int4{Int32: *req.MaxConcurrentTasks, Valid: true} } - if req.Tools != nil { - tools, _ := json.Marshal(req.Tools) - params.Tools = tools - } - if req.Triggers != nil { - triggers, _ := json.Marshal(req.Triggers) - params.Triggers = triggers - } agent, err := h.Queries.UpdateAgent(r.Context(), params) if err != nil { diff --git a/server/internal/handler/comment.go b/server/internal/handler/comment.go index e2f2f7fb..d7c3f900 100644 --- a/server/internal/handler/comment.go +++ b/server/internal/handler/comment.go @@ -418,10 +418,6 @@ func (h *Handler) enqueueMentionedAgentTasks(ctx context.Context, issue db.Issue } } } - // Check if the agent has on_mention trigger enabled. - if !agentHasTriggerEnabled(agent.Triggers, "on_mention") { - continue - } // Dedup: skip if this agent already has a pending task for this issue. hasPending, err := h.Queries.HasPendingTaskForIssueAndAgent(ctx, db.HasPendingTaskForIssueAndAgentParams{ IssueID: issue.ID, diff --git a/server/internal/handler/handler_test.go b/server/internal/handler/handler_test.go index 9a5ec3c8..8b798bc9 100644 --- a/server/internal/handler/handler_test.go +++ b/server/internal/handler/handler_test.go @@ -118,9 +118,9 @@ func setupHandlerTestFixture(ctx context.Context, pool *pgxpool.Pool) (string, s if _, err := pool.Exec(ctx, ` INSERT INTO agent ( workspace_id, name, description, runtime_mode, runtime_config, - runtime_id, visibility, max_concurrent_tasks, owner_id, tools, triggers + runtime_id, visibility, max_concurrent_tasks, owner_id ) - VALUES ($1, $2, '', 'cloud', '{}'::jsonb, $3, 'workspace', 1, $4, '[]'::jsonb, '[]'::jsonb) + VALUES ($1, $2, '', 'cloud', '{}'::jsonb, $3, 'workspace', 1, $4) `, workspaceID, "Handler Test Agent", runtimeID, userID); err != nil { return "", "", err } diff --git a/server/internal/handler/issue.go b/server/internal/handler/issue.go index 389bbede..af7fc638 100644 --- a/server/internal/handler/issue.go +++ b/server/internal/handler/issue.go @@ -39,23 +39,6 @@ type IssueResponse struct { Attachments []AttachmentResponse `json:"attachments,omitempty"` } -type agentTriggerSnapshot struct { - Type string `json:"type"` - Enabled bool `json:"enabled"` - Config map[string]any `json:"config"` -} - -// defaultAgentTriggers returns the default trigger config for new agents: -// all three triggers explicitly enabled. -func defaultAgentTriggers() []byte { - b, _ := json.Marshal([]agentTriggerSnapshot{ - {Type: "on_assign", Enabled: true}, - {Type: "on_comment", Enabled: true}, - {Type: "on_mention", Enabled: true}, - }) - return b -} - func issueToResponse(i db.Issue, issuePrefix string) IssueResponse { identifier := issuePrefix + "-" + strconv.Itoa(int(i.Number)) return IssueResponse{ @@ -549,8 +532,9 @@ func (h *Handler) canAssignAgent(ctx context.Context, r *http.Request, agentID, // the assigned agent. No status gate — assignment is an explicit human action, // so it should trigger regardless of issue status (e.g. assigning an agent to // a done issue to fix a discovered problem). +// All trigger types (on_assign, on_comment, on_mention) are always enabled. func (h *Handler) shouldEnqueueAgentTask(ctx context.Context, issue db.Issue) bool { - return h.isAgentTriggerEnabled(ctx, issue, "on_assign") + return h.isAgentAssigneeReady(ctx, issue) } // shouldEnqueueOnComment returns true if a member comment on this issue should @@ -561,7 +545,7 @@ func (h *Handler) shouldEnqueueOnComment(ctx context.Context, issue db.Issue) bo if issue.Status == "done" || issue.Status == "cancelled" { return false } - if !h.isAgentTriggerEnabled(ctx, issue, "on_comment") { + if !h.isAgentAssigneeReady(ctx, issue) { return false } // Coalescing queue: allow enqueue when a task is running (so the agent @@ -574,10 +558,9 @@ func (h *Handler) shouldEnqueueOnComment(ctx context.Context, issue db.Issue) bo return true } -// isAgentTriggerEnabled checks if an issue is assigned to an agent with a -// specific trigger type enabled. Returns true if the agent has no triggers -// configured (default-enabled behavior for backwards compatibility). -func (h *Handler) isAgentTriggerEnabled(ctx context.Context, issue db.Issue, triggerType string) bool { +// isAgentAssigneeReady checks if an issue is assigned to an active agent +// with a valid runtime. +func (h *Handler) isAgentAssigneeReady(ctx context.Context, issue db.Issue) bool { if !issue.AssigneeType.Valid || issue.AssigneeType.String != "agent" || !issue.AssigneeID.Valid { return false } @@ -587,43 +570,7 @@ func (h *Handler) isAgentTriggerEnabled(ctx context.Context, issue db.Issue, tri return false } - return agentHasTriggerEnabled(agent.Triggers, triggerType) -} - -// isAgentMentionTriggerEnabled checks if a specific agent has the on_mention -// trigger enabled. Unlike isAgentTriggerEnabled, this takes an explicit agent -// ID rather than deriving it from the issue assignee. -func (h *Handler) isAgentMentionTriggerEnabled(ctx context.Context, agentID pgtype.UUID) bool { - agent, err := h.Queries.GetAgent(ctx, agentID) - if err != nil || !agent.RuntimeID.Valid { - return false - } - - return agentHasTriggerEnabled(agent.Triggers, "on_mention") -} - -// agentHasTriggerEnabled checks if a trigger type is enabled in the agent's -// trigger config. Returns true (default-enabled) when the triggers list is -// empty or does not contain the requested type — for backwards compatibility -// with agents created before explicit trigger config was introduced. -func agentHasTriggerEnabled(raw []byte, triggerType string) bool { - if raw == nil || len(raw) == 0 { - return true - } - - var triggers []agentTriggerSnapshot - if err := json.Unmarshal(raw, &triggers); err != nil { - return false - } - if len(triggers) == 0 { - return true // Empty array = default-enabled (backwards compat) - } - for _, trigger := range triggers { - if trigger.Type == triggerType { - return trigger.Enabled - } - } - return true // Trigger type not configured = enabled by default + return true } func (h *Handler) DeleteIssue(w http.ResponseWriter, r *http.Request) { diff --git a/server/internal/handler/trigger_test.go b/server/internal/handler/trigger_test.go index d56a07c8..a735f403 100644 --- a/server/internal/handler/trigger_test.go +++ b/server/internal/handler/trigger_test.go @@ -1,7 +1,6 @@ package handler import ( - "encoding/json" "fmt" "testing" @@ -268,119 +267,3 @@ func TestOnCommentTriggerDecision(t *testing.T) { } } -// ------------------------------------------------------------------- -// agentHasTriggerEnabled -// ------------------------------------------------------------------- - -func TestAgentHasTriggerEnabled(t *testing.T) { - tests := []struct { - name string - raw []byte - triggerType string - want bool - }{ - { - name: "nil triggers → enabled (backwards compat)", - raw: nil, - triggerType: "on_comment", - want: true, - }, - { - name: "empty byte slice → enabled", - raw: []byte{}, - triggerType: "on_comment", - want: true, - }, - { - name: "empty JSON array → enabled (backwards compat)", - raw: []byte("[]"), - triggerType: "on_comment", - want: true, - }, - { - name: "on_comment explicitly enabled", - raw: mustJSON([]agentTriggerSnapshot{{Type: "on_comment", Enabled: true}}), - triggerType: "on_comment", - want: true, - }, - { - name: "on_comment explicitly disabled", - raw: mustJSON([]agentTriggerSnapshot{{Type: "on_comment", Enabled: false}}), - triggerType: "on_comment", - want: false, - }, - { - name: "on_mention not configured but others are → enabled by default", - raw: mustJSON([]agentTriggerSnapshot{{Type: "on_comment", Enabled: true}}), - triggerType: "on_mention", - want: true, - }, - { - name: "invalid JSON → disabled (fail safe)", - raw: []byte("{bad json"), - triggerType: "on_comment", - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := agentHasTriggerEnabled(tt.raw, tt.triggerType) - if got != tt.want { - t.Errorf("agentHasTriggerEnabled() = %v, want %v", got, tt.want) - } - }) - } -} - -// ------------------------------------------------------------------- -// defaultAgentTriggers -// ------------------------------------------------------------------- - -func TestDefaultAgentTriggers(t *testing.T) { - raw := defaultAgentTriggers() - - var triggers []agentTriggerSnapshot - if err := json.Unmarshal(raw, &triggers); err != nil { - t.Fatalf("failed to unmarshal default triggers: %v", err) - } - - if len(triggers) != 3 { - t.Fatalf("expected 3 default triggers, got %d", len(triggers)) - } - - expected := map[string]bool{ - "on_assign": true, - "on_comment": true, - "on_mention": true, - } - for _, tr := range triggers { - want, ok := expected[tr.Type] - if !ok { - t.Errorf("unexpected trigger type: %s", tr.Type) - continue - } - if tr.Enabled != want { - t.Errorf("trigger %s: enabled = %v, want %v", tr.Type, tr.Enabled, want) - } - delete(expected, tr.Type) - } - for typ := range expected { - t.Errorf("missing trigger type: %s", typ) - } - - // Verify all triggers are enabled via agentHasTriggerEnabled - for _, typ := range []string{"on_assign", "on_comment", "on_mention"} { - if !agentHasTriggerEnabled(raw, typ) { - t.Errorf("agentHasTriggerEnabled(default, %q) = false, want true", typ) - } - } -} - -func mustJSON(v any) []byte { - b, err := json.Marshal(v) - if err != nil { - panic(err) - } - return b -} diff --git a/server/internal/service/task.go b/server/internal/service/task.go index 92d602cf..7199dbfc 100644 --- a/server/internal/service/task.go +++ b/server/internal/service/task.go @@ -531,14 +531,6 @@ func agentToMap(a db.Agent) map[string]any { if a.RuntimeConfig != nil { json.Unmarshal(a.RuntimeConfig, &rc) } - var tools any - if a.Tools != nil { - json.Unmarshal(a.Tools, &tools) - } - var triggers any - if a.Triggers != nil { - json.Unmarshal(a.Triggers, &triggers) - } return map[string]any{ "id": util.UUIDToString(a.ID), "workspace_id": util.UUIDToString(a.WorkspaceID), @@ -553,8 +545,6 @@ func agentToMap(a db.Agent) map[string]any { "max_concurrent_tasks": a.MaxConcurrentTasks, "owner_id": util.UUIDToPtr(a.OwnerID), "skills": []any{}, - "tools": tools, - "triggers": triggers, "created_at": util.TimestampToString(a.CreatedAt), "updated_at": util.TimestampToString(a.UpdatedAt), "archived_at": util.TimestampToPtr(a.ArchivedAt), diff --git a/server/migrations/032_drop_agent_triggers.down.sql b/server/migrations/032_drop_agent_triggers.down.sql new file mode 100644 index 00000000..7ea29772 --- /dev/null +++ b/server/migrations/032_drop_agent_triggers.down.sql @@ -0,0 +1,3 @@ +-- Re-add the triggers and tools columns to agent table. +ALTER TABLE agent ADD COLUMN triggers JSONB NOT NULL DEFAULT '[]'; +ALTER TABLE agent ADD COLUMN tools JSONB NOT NULL DEFAULT '[]'; diff --git a/server/migrations/032_drop_agent_triggers.up.sql b/server/migrations/032_drop_agent_triggers.up.sql new file mode 100644 index 00000000..dc9d5b13 --- /dev/null +++ b/server/migrations/032_drop_agent_triggers.up.sql @@ -0,0 +1,5 @@ +-- Remove the triggers and tools columns from agent table. +-- Trigger behavior (on_assign, on_comment, on_mention) is now always enabled (hardcoded). +-- Tools was a placeholder field never used at runtime. +ALTER TABLE agent DROP COLUMN IF EXISTS triggers; +ALTER TABLE agent DROP COLUMN IF EXISTS tools; diff --git a/server/pkg/db/generated/agent.sql.go b/server/pkg/db/generated/agent.sql.go index bc785374..5099c3a8 100644 --- a/server/pkg/db/generated/agent.sql.go +++ b/server/pkg/db/generated/agent.sql.go @@ -14,7 +14,7 @@ import ( const archiveAgent = `-- name: ArchiveAgent :one UPDATE agent SET archived_at = now(), archived_by = $2, updated_at = now() WHERE id = $1 -RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by +RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by ` type ArchiveAgentParams struct { @@ -39,8 +39,6 @@ func (q *Queries) ArchiveAgent(ctx context.Context, arg ArchiveAgentParams) (Age &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -207,9 +205,9 @@ const createAgent = `-- name: CreateAgent :one INSERT INTO agent ( workspace_id, name, description, avatar_url, runtime_mode, runtime_config, runtime_id, visibility, max_concurrent_tasks, owner_id, - tools, triggers, instructions -) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) -RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by + instructions +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by ` type CreateAgentParams struct { @@ -223,8 +221,6 @@ type CreateAgentParams struct { Visibility string `json:"visibility"` MaxConcurrentTasks int32 `json:"max_concurrent_tasks"` OwnerID pgtype.UUID `json:"owner_id"` - Tools []byte `json:"tools"` - Triggers []byte `json:"triggers"` Instructions string `json:"instructions"` } @@ -240,8 +236,6 @@ func (q *Queries) CreateAgent(ctx context.Context, arg CreateAgentParams) (Agent arg.Visibility, arg.MaxConcurrentTasks, arg.OwnerID, - arg.Tools, - arg.Triggers, arg.Instructions, ) var i Agent @@ -259,8 +253,6 @@ func (q *Queries) CreateAgent(ctx context.Context, arg CreateAgentParams) (Agent &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -392,7 +384,7 @@ func (q *Queries) FailStaleTasks(ctx context.Context, arg FailStaleTasksParams) } const getAgent = `-- name: GetAgent :one -SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by FROM agent +SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by FROM agent WHERE id = $1 ` @@ -413,8 +405,6 @@ func (q *Queries) GetAgent(ctx context.Context, id pgtype.UUID) (Agent, error) { &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -424,7 +414,7 @@ func (q *Queries) GetAgent(ctx context.Context, id pgtype.UUID) (Agent, error) { } const getAgentInWorkspace = `-- name: GetAgentInWorkspace :one -SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by FROM agent +SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by FROM agent WHERE id = $1 AND workspace_id = $2 ` @@ -450,8 +440,6 @@ func (q *Queries) GetAgentInWorkspace(ctx context.Context, arg GetAgentInWorkspa &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -650,7 +638,7 @@ func (q *Queries) ListAgentTasks(ctx context.Context, agentID pgtype.UUID) ([]Ag } const listAgents = `-- name: ListAgents :many -SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by FROM agent +SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by FROM agent WHERE workspace_id = $1 AND archived_at IS NULL ORDER BY created_at ASC ` @@ -678,8 +666,6 @@ func (q *Queries) ListAgents(ctx context.Context, workspaceID pgtype.UUID) ([]Ag &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -696,7 +682,7 @@ func (q *Queries) ListAgents(ctx context.Context, workspaceID pgtype.UUID) ([]Ag } const listAllAgents = `-- name: ListAllAgents :many -SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by FROM agent +SELECT id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by FROM agent WHERE workspace_id = $1 ORDER BY created_at ASC ` @@ -724,8 +710,6 @@ func (q *Queries) ListAllAgents(ctx context.Context, workspaceID pgtype.UUID) ([ &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -830,7 +814,7 @@ func (q *Queries) ListTasksByIssue(ctx context.Context, issueID pgtype.UUID) ([] const restoreAgent = `-- name: RestoreAgent :one UPDATE agent SET archived_at = NULL, archived_by = NULL, updated_at = now() WHERE id = $1 -RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by +RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by ` func (q *Queries) RestoreAgent(ctx context.Context, id pgtype.UUID) (Agent, error) { @@ -850,8 +834,6 @@ func (q *Queries) RestoreAgent(ctx context.Context, id pgtype.UUID) (Agent, erro &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -902,12 +884,10 @@ UPDATE agent SET visibility = COALESCE($8, visibility), status = COALESCE($9, status), max_concurrent_tasks = COALESCE($10, max_concurrent_tasks), - tools = COALESCE($11, tools), - triggers = COALESCE($12, triggers), - instructions = COALESCE($13, instructions), + instructions = COALESCE($11, instructions), updated_at = now() WHERE id = $1 -RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by +RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by ` type UpdateAgentParams struct { @@ -921,8 +901,6 @@ type UpdateAgentParams struct { Visibility pgtype.Text `json:"visibility"` Status pgtype.Text `json:"status"` MaxConcurrentTasks pgtype.Int4 `json:"max_concurrent_tasks"` - Tools []byte `json:"tools"` - Triggers []byte `json:"triggers"` Instructions pgtype.Text `json:"instructions"` } @@ -938,8 +916,6 @@ func (q *Queries) UpdateAgent(ctx context.Context, arg UpdateAgentParams) (Agent arg.Visibility, arg.Status, arg.MaxConcurrentTasks, - arg.Tools, - arg.Triggers, arg.Instructions, ) var i Agent @@ -957,8 +933,6 @@ func (q *Queries) UpdateAgent(ctx context.Context, arg UpdateAgentParams) (Agent &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, @@ -970,7 +944,7 @@ func (q *Queries) UpdateAgent(ctx context.Context, arg UpdateAgentParams) (Agent const updateAgentStatus = `-- name: UpdateAgentStatus :one UPDATE agent SET status = $2, updated_at = now() WHERE id = $1 -RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, tools, triggers, runtime_id, instructions, archived_at, archived_by +RETURNING id, workspace_id, name, avatar_url, runtime_mode, runtime_config, visibility, status, max_concurrent_tasks, owner_id, created_at, updated_at, description, runtime_id, instructions, archived_at, archived_by ` type UpdateAgentStatusParams struct { @@ -995,8 +969,6 @@ func (q *Queries) UpdateAgentStatus(ctx context.Context, arg UpdateAgentStatusPa &i.CreatedAt, &i.UpdatedAt, &i.Description, - &i.Tools, - &i.Triggers, &i.RuntimeID, &i.Instructions, &i.ArchivedAt, diff --git a/server/pkg/db/generated/models.go b/server/pkg/db/generated/models.go index 1aa34fe7..40242b19 100644 --- a/server/pkg/db/generated/models.go +++ b/server/pkg/db/generated/models.go @@ -33,8 +33,6 @@ type Agent struct { CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` Description string `json:"description"` - Tools []byte `json:"tools"` - Triggers []byte `json:"triggers"` RuntimeID pgtype.UUID `json:"runtime_id"` Instructions string `json:"instructions"` ArchivedAt pgtype.Timestamptz `json:"archived_at"` diff --git a/server/pkg/db/queries/agent.sql b/server/pkg/db/queries/agent.sql index 95239b2f..9150ba64 100644 --- a/server/pkg/db/queries/agent.sql +++ b/server/pkg/db/queries/agent.sql @@ -20,8 +20,8 @@ WHERE id = $1 AND workspace_id = $2; INSERT INTO agent ( workspace_id, name, description, avatar_url, runtime_mode, runtime_config, runtime_id, visibility, max_concurrent_tasks, owner_id, - tools, triggers, instructions -) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) + instructions +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *; -- name: UpdateAgent :one @@ -35,8 +35,6 @@ UPDATE agent SET visibility = COALESCE(sqlc.narg('visibility'), visibility), status = COALESCE(sqlc.narg('status'), status), max_concurrent_tasks = COALESCE(sqlc.narg('max_concurrent_tasks'), max_concurrent_tasks), - tools = COALESCE(sqlc.narg('tools'), tools), - triggers = COALESCE(sqlc.narg('triggers'), triggers), instructions = COALESCE(sqlc.narg('instructions'), instructions), updated_at = now() WHERE id = $1
- External tools and APIs this agent can use during task execution. -
No tools configured
- Configure when this agent should start working. -