Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
65335c4
feat: plumb per-call tool-call ID into embedded MCP metadata; seed as…
cursoragent Jul 17, 2026
50c2ee9
feat: ask_agent MCP tool, delegation service core, and delegated sub-…
cursoragent Jul 17, 2026
b5b6082
webapp: delegation card with live delegation_update progress and stat…
cursoragent Jul 17, 2026
494c0f0
test: delegation service validation/state, ask_agent resolver, activi…
cursoragent Jul 17, 2026
e7adbb6
test: delegation sub-turn filtering, pending detection, and accepted-…
cursoragent Jul 17, 2026
f2a56b3
fix: nudge conversation refetch when async tool batches finish withou…
cursoragent Jul 17, 2026
19ff605
webapp: reconcile terminal delegation cards for permalink and agent i…
cursoragent Jul 17, 2026
20c10f3
e2e: agent delegation spec (DM, nested approval, channel share) + sha…
cursoragent Jul 17, 2026
2974e68
eval: agent delegates via ask_agent when appropriate, answers directl…
cursoragent Jul 17, 2026
d46fc3a
docs: document ask_agent delegation in the admin guide
cursoragent Jul 17, 2026
8139a34
style: gofmt and shadow fixes
cursoragent Jul 17, 2026
34da370
fix: scope conversation refetch nudge to asynchronously executed batches
cursoragent Jul 17, 2026
e2492b5
webapp: delegation card unit tests and explicit i18n ids
cursoragent Jul 17, 2026
50643de
test: initiator-only delegation status derivation
cursoragent Jul 17, 2026
586d78e
fix: address review feedback on identity coupling, record durability,…
cursoragent Jul 21, 2026
56f84ef
fix: claim tool approvals atomically with a content compare-and-set
cursoragent Jul 21, 2026
9f5650d
test: cover the tool-approval content claim in fakes and postgres store
cursoragent Jul 21, 2026
7556d5b
test: implement content claim in remaining conversation store fakes
cursoragent Jul 21, 2026
cc34b93
test: table-driven waiter registry; assert delegated task fidelity in…
cursoragent Jul 21, 2026
40a869c
webapp: exclusive delegation card status, accessible task toggle, loc…
cursoragent Jul 21, 2026
d7a6c66
style: avoid identical-expression lint in waiter registry test
cursoragent Jul 21, 2026
9bae590
fix: make delegation posts silent
cursoragent Jul 22, 2026
d2dd7ab
test: cover silent delegation posts
cursoragent Jul 22, 2026
7ed66e7
fix: harden delegated approval continuations
cursoragent Jul 23, 2026
8c3ef0f
test: make conversation CAS fakes faithful
cursoragent Jul 23, 2026
64711fd
test: remove redundant raw message conversion
cursoragent Jul 23, 2026
42e7fab
test: preserve raw message assertion types
cursoragent Jul 23, 2026
cbfae5f
feat: allow agents to delegate to themselves
cursoragent Jul 23, 2026
a09553f
fix: claim delegated approvals across UI surfaces
cursoragent Jul 23, 2026
82f6c22
webapp: embed delegated approvals in parent cards
cursoragent Jul 23, 2026
4bfee6c
test: satisfy inline approval lint
cursoragent Jul 23, 2026
982a321
test: describe inline delegated approval flow
cursoragent Jul 23, 2026
45367d7
Merge origin/master into agent delegation branch
cursoragent Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
group: e2e-shard-4
name: e2e-${{ matrix.shard.name }}
env:
MM_IMAGE: ${{ vars.MM_IMAGE || 'mattermostdevelopment/mattermost-enterprise-edition:release-11.9' }}
MM_IMAGE: ${{ vars.MM_IMAGE || 'mattermostdevelopment/mattermost-enterprise-edition:release-11.10' }}
defaults:
run:
working-directory: ./e2e
Expand Down
3 changes: 3 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/mattermost/mattermost-plugin-agents/v2/conversation"
"github.com/mattermost/mattermost-plugin-agents/v2/conversations"
"github.com/mattermost/mattermost-plugin-agents/v2/customprompts"
"github.com/mattermost/mattermost-plugin-agents/v2/delegation"
"github.com/mattermost/mattermost-plugin-agents/v2/embeddings"
"github.com/mattermost/mattermost-plugin-agents/v2/enterprise"
"github.com/mattermost/mattermost-plugin-agents/v2/files"
Expand Down Expand Up @@ -162,6 +163,7 @@ type API struct {
streamStopNotifier StreamStopClusterNotifier
conversationStore ConversationStore
convService *conversation.Service
delegationService *delegation.Service
getSearchInitError func() string
customPromptsStore *customprompts.Store

Expand Down Expand Up @@ -297,6 +299,7 @@ func (a *API) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Reques

router.GET("/conversations/:conversationid", a.handleGetConversation)
router.GET("/conversations/:conversationid/context", a.handleGetConversationContext)
router.GET("/delegations/:parenttoolcallid", a.handleGetDelegationStatus)

router.GET("/oauth/callback", a.handleOAuthCallback)
router.GET("/ai_threads", a.handleGetAIThreads)
Expand Down
49 changes: 49 additions & 0 deletions api/api_delegation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2023-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

package api

import (
"errors"
"net/http"

"github.com/gin-gonic/gin"
"github.com/mattermost/mattermost-plugin-agents/v2/delegation"
)

// SetDelegationService sets the delegation service used by the delegation
// status endpoint.
func (a *API) SetDelegationService(svc *delegation.Service) {
a.delegationService = svc
}

// handleGetDelegationStatus returns the live status of a delegation keyed by
// the parent ask_agent tool call ID. Initiator-only: any other user (or an
// unknown ID) gets a 404 so existence is not leaked.
func (a *API) handleGetDelegationStatus(c *gin.Context) {
userID := c.GetHeader("Mattermost-User-Id")
parentToolCallID := c.Param("parenttoolcallid")

if a.delegationService == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}

status, err := a.delegationService.StatusByParentToolCall(userID, parentToolCallID)
if err != nil {
if errors.Is(err, delegation.ErrNotConfigured) {
c.AbortWithStatus(http.StatusNotFound)
return
}
c.AbortWithError(http.StatusInternalServerError, err)
return
}
if status == nil {
// Expected for expired records, foreign users, and quick reconcile
// races — not an error worth logging.
c.AbortWithStatus(http.StatusNotFound)
return
}

c.JSON(http.StatusOK, status)
}
4 changes: 4 additions & 0 deletions api/api_no_tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ func (m *mockConvServiceStore) UpdateTurnContent(id string, content json.RawMess
return nil
}

func (m *mockConvServiceStore) UpdateTurnContentIfMatches(id string, expected, updated json.RawMessage) (bool, error) {
return true, nil
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
func (m *mockConvServiceStore) UpdateTurnTokens(_ string, _, _ int64) error {
return nil
}
Expand Down
7 changes: 7 additions & 0 deletions channels/analysis_conversation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ func (s *inMemoryStore) UpdateTurnContent(id string, content json.RawMessage) er
return nil
}

func (s *inMemoryStore) UpdateTurnContentIfMatches(id string, _, updated json.RawMessage) (bool, error) {
if err := s.UpdateTurnContent(id, updated); err != nil {
return false, err
}
return true, nil
}

func (s *inMemoryStore) UpdateTurnTokens(id string, tokensIn, tokensOut int64) error {
turn, ok := s.turns[id]
if !ok {
Expand Down
4 changes: 4 additions & 0 deletions channels/channels_eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ func (s *evalInMemoryStore) GetTurnsForConversation(conversationID string) ([]st
return turns, nil
}

func (s *evalInMemoryStore) UpdateTurnContentIfMatches(id string, _, updated json.RawMessage) (bool, error) {
return true, s.UpdateTurnContent(id, updated)
}

func (s *evalInMemoryStore) UpdateTurnContent(id string, content json.RawMessage) error {
if t, ok := s.turns[id]; ok {
t.Content = content
Expand Down
9 changes: 9 additions & 0 deletions conversation/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Store interface {
GetTurnsForConversation(conversationID string) ([]store.Turn, error)
GetTurnByPostID(postID string) (*store.Turn, error)
UpdateTurnContent(id string, content json.RawMessage) error
UpdateTurnContentIfMatches(id string, expected, updated json.RawMessage) (bool, error)
UpdateTurnTokens(id string, tokensIn, tokensOut int64) error
UpdateTurnPostID(id string, postID *string) error
DeleteResponseTurns(conversationID, postID string) error
Expand Down Expand Up @@ -204,6 +205,14 @@ func (s *Service) UpdateTurnContent(turnID string, content json.RawMessage) erro
return s.store.UpdateTurnContent(turnID, content)
}

// ClaimTurnContent atomically replaces a turn's content only while it still
// equals expected, returning whether this caller won the claim. Losing the
// claim means another request (possibly on another node) already resolved the
// same blocks.
func (s *Service) ClaimTurnContent(turnID string, expected, updated json.RawMessage) (bool, error) {
return s.store.UpdateTurnContentIfMatches(turnID, expected, updated)
}

// CreateTurn persists a new turn in the store with an explicit sequence.
func (s *Service) CreateTurn(turn *store.Turn) error {
return s.store.CreateTurn(turn)
Expand Down
25 changes: 13 additions & 12 deletions conversations/conversations.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,19 @@ type ConfigProvider interface {
}

type Conversations struct {
prompts *llm.Prompts
mmClient mmapi.Client
streamingService streaming.Service
contextBuilder *llmcontext.Builder
bots *bots.MMBots
db *mmapi.DBClient
licenseChecker *enterprise.LicenseChecker
i18n *i18n.Bundle
meetingsService MeetingsService
configProvider ConfigProvider
toolPolicyChecker mcp.ToolPolicyChecker
convService *conversation.Service
prompts *llm.Prompts
mmClient mmapi.Client
streamingService streaming.Service
contextBuilder *llmcontext.Builder
bots *bots.MMBots
db *mmapi.DBClient
licenseChecker *enterprise.LicenseChecker
i18n *i18n.Bundle
meetingsService MeetingsService
configProvider ConfigProvider
toolPolicyChecker mcp.ToolPolicyChecker
convService *conversation.Service
delegationNotifier DelegationNotifier
}

// MeetingsService defines the interface for meetings functionality needed by conversations
Expand Down
Loading
Loading