feat(agentic): add direct workspace messaging

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 21:04:39 +00:00
parent 3a9834ec03
commit b0662c282b
6 changed files with 594 additions and 1 deletions

View file

@ -0,0 +1,131 @@
// SPDX-License-Identifier: EUPL-1.2
package agentic
import core "dappco.re/go/core"
func (s *PrepSubsystem) cmdMessageSend(options core.Options) core.Result {
workspace := optionStringValue(options, "workspace", "_arg")
fromAgent := optionStringValue(options, "from_agent", "from")
toAgent := optionStringValue(options, "to_agent", "to")
content := optionStringValue(options, "content", "body")
if workspace == "" || fromAgent == "" || toAgent == "" || core.Trim(content) == "" {
core.Print(nil, "usage: core-agent message send <workspace> --from=codex --to=claude --subject=\"Review\" --content=\"Please check the prompt.\"")
return core.Result{Value: core.E("agentic.cmdMessageSend", "workspace, from_agent, to_agent, and content are required", nil), OK: false}
}
result := s.handleMessageSend(s.commandContext(), core.NewOptions(
core.Option{Key: "workspace", Value: workspace},
core.Option{Key: "from_agent", Value: fromAgent},
core.Option{Key: "to_agent", Value: toAgent},
core.Option{Key: "subject", Value: optionStringValue(options, "subject")},
core.Option{Key: "content", Value: content},
))
if !result.OK {
err := commandResultError("agentic.cmdMessageSend", result)
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
output, ok := result.Value.(MessageSendOutput)
if !ok {
err := core.E("agentic.cmdMessageSend", "invalid message send output", nil)
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
core.Print(nil, "sent: %s", output.Message.ID)
core.Print(nil, "from: %s", output.Message.FromAgent)
core.Print(nil, "to: %s", output.Message.ToAgent)
if output.Message.Subject != "" {
core.Print(nil, "subject: %s", output.Message.Subject)
}
core.Print(nil, "content: %s", output.Message.Content)
return core.Result{Value: output, OK: true}
}
func (s *PrepSubsystem) cmdMessageInbox(options core.Options) core.Result {
workspace := optionStringValue(options, "workspace", "_arg")
agent := optionStringValue(options, "agent", "agent_id", "agent-id")
if workspace == "" || agent == "" {
core.Print(nil, "usage: core-agent message inbox <workspace> --agent=claude [--limit=50]")
return core.Result{Value: core.E("agentic.cmdMessageInbox", "workspace and agent are required", nil), OK: false}
}
result := s.handleMessageInbox(s.commandContext(), core.NewOptions(
core.Option{Key: "workspace", Value: workspace},
core.Option{Key: "agent", Value: agent},
core.Option{Key: "limit", Value: optionIntValue(options, "limit")},
))
if !result.OK {
err := commandResultError("agentic.cmdMessageInbox", result)
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
output, ok := result.Value.(MessageListOutput)
if !ok {
err := core.E("agentic.cmdMessageInbox", "invalid message inbox output", nil)
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
if len(output.Messages) == 0 {
core.Print(nil, "no messages")
return core.Result{Value: output, OK: true}
}
core.Print(nil, "count: %d", output.Count)
for _, message := range output.Messages {
core.Print(nil, " [%s] %s -> %s", message.CreatedAt, message.FromAgent, message.ToAgent)
if message.Subject != "" {
core.Print(nil, " subject: %s", message.Subject)
}
core.Print(nil, " %s", message.Content)
}
return core.Result{Value: output, OK: true}
}
func (s *PrepSubsystem) cmdMessageConversation(options core.Options) core.Result {
workspace := optionStringValue(options, "workspace", "_arg")
agent := optionStringValue(options, "agent", "agent_id", "agent-id")
withAgent := optionStringValue(options, "with_agent", "with-agent", "with", "to_agent", "to-agent")
if workspace == "" || agent == "" || withAgent == "" {
core.Print(nil, "usage: core-agent message conversation <workspace> --agent=codex --with=claude [--limit=50]")
return core.Result{Value: core.E("agentic.cmdMessageConversation", "workspace, agent, and with_agent are required", nil), OK: false}
}
result := s.handleMessageConversation(s.commandContext(), core.NewOptions(
core.Option{Key: "workspace", Value: workspace},
core.Option{Key: "agent", Value: agent},
core.Option{Key: "with_agent", Value: withAgent},
core.Option{Key: "limit", Value: optionIntValue(options, "limit")},
))
if !result.OK {
err := commandResultError("agentic.cmdMessageConversation", result)
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
output, ok := result.Value.(MessageListOutput)
if !ok {
err := core.E("agentic.cmdMessageConversation", "invalid message conversation output", nil)
core.Print(nil, "error: %v", err)
return core.Result{Value: err, OK: false}
}
if len(output.Messages) == 0 {
core.Print(nil, "no messages")
return core.Result{Value: output, OK: true}
}
core.Print(nil, "count: %d", output.Count)
for _, message := range output.Messages {
core.Print(nil, " [%s] %s -> %s", message.CreatedAt, message.FromAgent, message.ToAgent)
if message.Subject != "" {
core.Print(nil, " subject: %s", message.Subject)
}
core.Print(nil, " %s", message.Content)
}
return core.Result{Value: output, OK: true}
}

View file

@ -13,6 +13,12 @@ func (s *PrepSubsystem) registerPlatformCommands() {
c.Command("sync/status", core.Command{Description: "Show platform sync status for the current or named agent", Action: s.cmdSyncStatus})
c.Command("auth/provision", core.Command{Description: "Provision a platform API key for an authenticated agent user", Action: s.cmdAuthProvision})
c.Command("auth/revoke", core.Command{Description: "Revoke a platform API key", Action: s.cmdAuthRevoke})
c.Command("message/send", core.Command{Description: "Send a direct message to another agent", Action: s.cmdMessageSend})
c.Command("messages/send", core.Command{Description: "Send a direct message to another agent", Action: s.cmdMessageSend})
c.Command("message/inbox", core.Command{Description: "List direct messages for an agent", Action: s.cmdMessageInbox})
c.Command("messages/inbox", core.Command{Description: "List direct messages for an agent", Action: s.cmdMessageInbox})
c.Command("message/conversation", core.Command{Description: "List a direct conversation between two agents", Action: s.cmdMessageConversation})
c.Command("messages/conversation", core.Command{Description: "List a direct conversation between two agents", Action: s.cmdMessageConversation})
c.Command("fleet/register", core.Command{Description: "Register a fleet node with the platform API", Action: s.cmdFleetRegister})
c.Command("fleet/heartbeat", core.Command{Description: "Send a heartbeat for a registered fleet node", Action: s.cmdFleetHeartbeat})

336
pkg/agentic/message.go Normal file
View file

@ -0,0 +1,336 @@
// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"context"
"sort"
"time"
core "dappco.re/go/core"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// message := agentic.AgentMessage{Workspace: "core/go-io/task-5", FromAgent: "codex", ToAgent: "claude", Subject: "Review", Content: "Please check the prompt."}
type AgentMessage struct {
ID string `json:"id"`
Workspace string `json:"workspace"`
FromAgent string `json:"from_agent"`
ToAgent string `json:"to_agent"`
Subject string `json:"subject,omitempty"`
Content string `json:"content"`
ReadAt string `json:"read_at,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
}
// input := agentic.MessageSendInput{Workspace: "core/go-io/task-5", FromAgent: "codex", ToAgent: "claude", Subject: "Review", Content: "Please check the prompt."}
type MessageSendInput struct {
Workspace string `json:"workspace"`
FromAgent string `json:"from_agent"`
ToAgent string `json:"to_agent"`
Subject string `json:"subject,omitempty"`
Content string `json:"content"`
}
// input := agentic.MessageInboxInput{Workspace: "core/go-io/task-5", Agent: "claude"}
type MessageInboxInput struct {
Workspace string `json:"workspace"`
Agent string `json:"agent"`
Limit int `json:"limit,omitempty"`
}
// input := agentic.MessageConversationInput{Workspace: "core/go-io/task-5", Agent: "codex", WithAgent: "claude"}
type MessageConversationInput struct {
Workspace string `json:"workspace"`
Agent string `json:"agent"`
WithAgent string `json:"with_agent"`
Limit int `json:"limit,omitempty"`
}
// out := agentic.MessageSendOutput{Success: true, Message: agentic.AgentMessage{ID: "msg-1"}}
type MessageSendOutput struct {
Success bool `json:"success"`
Message AgentMessage `json:"message"`
}
// out := agentic.MessageListOutput{Success: true, Count: 1, Messages: []agentic.AgentMessage{{ID: "msg-1"}}}
type MessageListOutput struct {
Success bool `json:"success"`
Count int `json:"count"`
Messages []AgentMessage `json:"messages"`
}
// result := c.Action("agentic.message.send").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
// core.Option{Key: "from_agent", Value: "codex"},
// core.Option{Key: "to_agent", Value: "claude"},
//
// ))
func (s *PrepSubsystem) handleMessageSend(ctx context.Context, options core.Options) core.Result {
_, output, err := s.messageSend(ctx, nil, MessageSendInput{
Workspace: optionStringValue(options, "workspace", "_arg"),
FromAgent: optionStringValue(options, "from_agent", "from"),
ToAgent: optionStringValue(options, "to_agent", "to"),
Subject: optionStringValue(options, "subject"),
Content: optionStringValue(options, "content", "body"),
})
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("agentic.message.inbox").Run(ctx, core.NewOptions(core.Option{Key: "workspace", Value: "core/go-io/task-5"}))
func (s *PrepSubsystem) handleMessageInbox(ctx context.Context, options core.Options) core.Result {
_, output, err := s.messageInbox(ctx, nil, MessageInboxInput{
Workspace: optionStringValue(options, "workspace", "_arg"),
Agent: optionStringValue(options, "agent", "agent_id", "agent-id"),
Limit: optionIntValue(options, "limit"),
})
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("agentic.message.conversation").Run(ctx, core.NewOptions(
//
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
// core.Option{Key: "agent", Value: "codex"},
// core.Option{Key: "with_agent", Value: "claude"},
//
// ))
func (s *PrepSubsystem) handleMessageConversation(ctx context.Context, options core.Options) core.Result {
_, output, err := s.messageConversation(ctx, nil, MessageConversationInput{
Workspace: optionStringValue(options, "workspace", "_arg"),
Agent: optionStringValue(options, "agent", "agent_id", "agent-id"),
WithAgent: optionStringValue(options, "with_agent", "with-agent", "with", "to_agent", "to-agent"),
Limit: optionIntValue(options, "limit"),
})
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
func (s *PrepSubsystem) registerMessageTools(server *mcp.Server) {
mcp.AddTool(server, &mcp.Tool{
Name: "agentic_message_send",
Description: "Send a direct message between two agents within a workspace.",
}, s.messageSend)
mcp.AddTool(server, &mcp.Tool{
Name: "agentic_message_inbox",
Description: "List messages delivered to an agent within a workspace.",
}, s.messageInbox)
mcp.AddTool(server, &mcp.Tool{
Name: "agentic_message_conversation",
Description: "List the chronological conversation between two agents within a workspace.",
}, s.messageConversation)
}
func (s *PrepSubsystem) messageSend(_ context.Context, _ *mcp.CallToolRequest, input MessageSendInput) (*mcp.CallToolResult, MessageSendOutput, error) {
message, err := messageStoreSend(input)
if err != nil {
return nil, MessageSendOutput{}, err
}
return nil, MessageSendOutput{Success: true, Message: message}, nil
}
func (s *PrepSubsystem) messageInbox(_ context.Context, _ *mcp.CallToolRequest, input MessageInboxInput) (*mcp.CallToolResult, MessageListOutput, error) {
messages, err := messageStoreInbox(input.Workspace, input.Agent, input.Limit)
if err != nil {
return nil, MessageListOutput{}, err
}
return nil, MessageListOutput{Success: true, Count: len(messages), Messages: messages}, nil
}
func (s *PrepSubsystem) messageConversation(_ context.Context, _ *mcp.CallToolRequest, input MessageConversationInput) (*mcp.CallToolResult, MessageListOutput, error) {
messages, err := messageStoreConversation(input.Workspace, input.Agent, input.WithAgent, input.Limit)
if err != nil {
return nil, MessageListOutput{}, err
}
return nil, MessageListOutput{Success: true, Count: len(messages), Messages: messages}, nil
}
func messageStoreSend(input MessageSendInput) (AgentMessage, error) {
if input.Workspace == "" {
return AgentMessage{}, core.E("messageSend", "workspace is required", nil)
}
if input.FromAgent == "" {
return AgentMessage{}, core.E("messageSend", "from_agent is required", nil)
}
if input.ToAgent == "" {
return AgentMessage{}, core.E("messageSend", "to_agent is required", nil)
}
if core.Trim(input.Content) == "" {
return AgentMessage{}, core.E("messageSend", "content is required", nil)
}
messages, err := readWorkspaceMessages(input.Workspace)
if err != nil {
return AgentMessage{}, err
}
now := time.Now().Format(time.RFC3339)
message := AgentMessage{
ID: messageID(),
Workspace: core.Trim(input.Workspace),
FromAgent: core.Trim(input.FromAgent),
ToAgent: core.Trim(input.ToAgent),
Subject: core.Trim(input.Subject),
Content: input.Content,
CreatedAt: now,
}
messages = append(messages, message)
if err := writeWorkspaceMessages(input.Workspace, messages); err != nil {
return AgentMessage{}, err
}
return message, nil
}
func messageStoreInbox(workspace, agent string, limit int) ([]AgentMessage, error) {
if workspace == "" {
return nil, core.E("messageInbox", "workspace is required", nil)
}
if agent == "" {
return nil, core.E("messageInbox", "agent is required", nil)
}
return messageStoreFilter(workspace, limit, func(message AgentMessage) bool {
return message.ToAgent == agent
})
}
func messageStoreConversation(workspace, agent, withAgent string, limit int) ([]AgentMessage, error) {
if workspace == "" {
return nil, core.E("messageConversation", "workspace is required", nil)
}
if agent == "" {
return nil, core.E("messageConversation", "agent is required", nil)
}
if withAgent == "" {
return nil, core.E("messageConversation", "with_agent is required", nil)
}
return messageStoreFilter(workspace, limit, func(message AgentMessage) bool {
return (message.FromAgent == agent && message.ToAgent == withAgent) || (message.FromAgent == withAgent && message.ToAgent == agent)
})
}
func messageStoreFilter(workspace string, limit int, match func(AgentMessage) bool) ([]AgentMessage, error) {
messages, err := readWorkspaceMessages(workspace)
if err != nil {
return nil, err
}
filtered := make([]AgentMessage, 0, len(messages))
for _, message := range messages {
message = normaliseAgentMessage(message)
if match(message) {
filtered = append(filtered, message)
}
}
sort.SliceStable(filtered, func(i, j int) bool {
return filtered[i].CreatedAt < filtered[j].CreatedAt
})
if limit <= 0 {
limit = 50
}
if len(filtered) > limit {
filtered = filtered[len(filtered)-limit:]
}
return filtered, nil
}
func messageRoot() string {
return core.JoinPath(CoreRoot(), "messages")
}
func messagePath(workspace string) string {
return core.JoinPath(messageRoot(), core.Concat(core.SanitisePath(workspace), ".json"))
}
func readWorkspaceMessages(workspace string) ([]AgentMessage, error) {
if workspace == "" {
return []AgentMessage{}, nil
}
result := fs.Read(messagePath(workspace))
if !result.OK {
err, _ := result.Value.(error)
if err == nil || core.Contains(err.Error(), "no such file") {
return []AgentMessage{}, nil
}
return nil, core.E("readWorkspaceMessages", "failed to read message store", err)
}
content := core.Trim(result.Value.(string))
if content == "" {
return []AgentMessage{}, nil
}
var messages []AgentMessage
if parseResult := core.JSONUnmarshalString(content, &messages); !parseResult.OK {
err, _ := parseResult.Value.(error)
return nil, core.E("readWorkspaceMessages", "failed to parse message store", err)
}
for i := range messages {
messages[i] = normaliseAgentMessage(messages[i])
}
sort.SliceStable(messages, func(i, j int) bool {
return messages[i].CreatedAt < messages[j].CreatedAt
})
return messages, nil
}
func writeWorkspaceMessages(workspace string, messages []AgentMessage) error {
if workspace == "" {
return core.E("writeWorkspaceMessages", "workspace is required", nil)
}
normalised := make([]AgentMessage, 0, len(messages))
for _, message := range messages {
normalised = append(normalised, normaliseAgentMessage(message))
}
if ensureDirResult := fs.EnsureDir(messageRoot()); !ensureDirResult.OK {
err, _ := ensureDirResult.Value.(error)
return core.E("writeWorkspaceMessages", "failed to create message store directory", err)
}
if writeResult := fs.WriteAtomic(messagePath(workspace), core.JSONMarshalString(normalised)); !writeResult.OK {
err, _ := writeResult.Value.(error)
return core.E("writeWorkspaceMessages", "failed to write message store", err)
}
return nil
}
func normaliseAgentMessage(message AgentMessage) AgentMessage {
message.Workspace = core.Trim(message.Workspace)
message.FromAgent = core.Trim(message.FromAgent)
message.ToAgent = core.Trim(message.ToAgent)
message.Subject = core.Trim(message.Subject)
if message.ID == "" {
message.ID = messageID()
}
if message.CreatedAt == "" {
message.CreatedAt = time.Now().Format(time.RFC3339)
}
return message
}
func messageID() string {
return core.Concat("msg-", core.Sprint(time.Now().UnixNano()))
}

View file

@ -0,0 +1,97 @@
// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"context"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMessage_MessageSend_Good_PersistsAndReadsBack(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
result := s.cmdMessageSend(core.NewOptions(
core.Option{Key: "_arg", Value: "core/go-io/task-5"},
core.Option{Key: "from", Value: "codex"},
core.Option{Key: "to", Value: "claude"},
core.Option{Key: "subject", Value: "Review"},
core.Option{Key: "content", Value: "Please check the prompt."},
))
require.True(t, result.OK)
output, ok := result.Value.(MessageSendOutput)
require.True(t, ok)
assert.Equal(t, "core/go-io/task-5", output.Message.Workspace)
assert.Equal(t, "codex", output.Message.FromAgent)
assert.Equal(t, "claude", output.Message.ToAgent)
assert.Equal(t, "Review", output.Message.Subject)
assert.Equal(t, "Please check the prompt.", output.Message.Content)
assert.NotEmpty(t, output.Message.ID)
assert.NotEmpty(t, output.Message.CreatedAt)
messageStorePath := messagePath("core/go-io/task-5")
assert.True(t, fs.Exists(messageStorePath))
inboxResult := s.cmdMessageInbox(core.NewOptions(
core.Option{Key: "_arg", Value: "core/go-io/task-5"},
core.Option{Key: "agent", Value: "claude"},
))
require.True(t, inboxResult.OK)
inbox, ok := inboxResult.Value.(MessageListOutput)
require.True(t, ok)
assert.Equal(t, 1, inbox.Count)
require.Len(t, inbox.Messages, 1)
assert.Equal(t, output.Message.ID, inbox.Messages[0].ID)
conversationResult := s.cmdMessageConversation(core.NewOptions(
core.Option{Key: "_arg", Value: "core/go-io/task-5"},
core.Option{Key: "agent", Value: "codex"},
core.Option{Key: "with", Value: "claude"},
))
require.True(t, conversationResult.OK)
conversation, ok := conversationResult.Value.(MessageListOutput)
require.True(t, ok)
assert.Equal(t, 1, conversation.Count)
require.Len(t, conversation.Messages, 1)
assert.Equal(t, output.Message.ID, conversation.Messages[0].ID)
}
func TestMessage_MessageSend_Bad_MissingRequiredFields(t *testing.T) {
s := newTestPrep(t)
result := s.cmdMessageSend(core.NewOptions(
core.Option{Key: "_arg", Value: "core/go-io/task-5"},
core.Option{Key: "from", Value: "codex"},
))
assert.False(t, result.OK)
require.Error(t, result.Value.(error))
assert.Contains(t, result.Value.(error).Error(), "required")
}
func TestMessage_MessageInbox_Ugly_CorruptStore(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
require.True(t, fs.EnsureDir(messageRoot()).OK)
require.True(t, fs.Write(messagePath("core/go-io/task-5"), "{broken json").OK)
result := s.handleMessageInbox(context.Background(), core.NewOptions(
core.Option{Key: "workspace", Value: "core/go-io/task-5"},
core.Option{Key: "agent", Value: "claude"},
))
assert.False(t, result.OK)
require.Error(t, result.Value.(error))
assert.Contains(t, result.Value.(error).Error(), "failed to parse message store")
}

View file

@ -94,7 +94,8 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
"agentic.prompt", "agentic.task", "agentic.flow", "agentic.persona",
"agentic.sync.status", "agentic.fleet.nodes", "agentic.fleet.stats", "agentic.fleet.events",
"agentic.credits.balance", "agentic.credits.history",
"agentic.subscription.detect", "agentic.subscription.budget":
"agentic.subscription.detect", "agentic.subscription.budget",
"agentic.message.send", "agentic.message.inbox", "agentic.message.conversation":
return core.Entitlement{Allowed: true, Unlimited: true}
}
if s.frozen {
@ -157,6 +158,12 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
c.Action("agent.subscription.budget", s.handleSubscriptionBudget).Description = "Get the compute budget for a fleet node"
c.Action("agentic.subscription.budget.update", s.handleSubscriptionBudgetUpdate).Description = "Update the compute budget for a fleet node"
c.Action("agent.subscription.budget.update", s.handleSubscriptionBudgetUpdate).Description = "Update the compute budget for a fleet node"
c.Action("agentic.message.send", s.handleMessageSend).Description = "Send a direct message between agents"
c.Action("agent.message.send", s.handleMessageSend).Description = "Send a direct message between agents"
c.Action("agentic.message.inbox", s.handleMessageInbox).Description = "List direct messages for an agent"
c.Action("agent.message.inbox", s.handleMessageInbox).Description = "List direct messages for an agent"
c.Action("agentic.message.conversation", s.handleMessageConversation).Description = "List a direct conversation between two agents"
c.Action("agent.message.conversation", s.handleMessageConversation).Description = "List a direct conversation between two agents"
c.Action("agentic.dispatch", s.handleDispatch).Description = "Prep workspace and spawn a subagent"
c.Action("agentic.dispatch.sync", s.handleDispatchSync).Description = "Dispatch a single task synchronously and block until it completes"
@ -378,6 +385,7 @@ func (s *PrepSubsystem) RegisterTools(server *mcp.Server) {
s.registerTaskTools(server)
s.registerTemplateTools(server)
s.registerIssueTools(server)
s.registerMessageTools(server)
s.registerSprintTools(server)
s.registerContentTools(server)
s.registerLanguageTools(server)

View file

@ -518,6 +518,12 @@ func TestPrep_OnStartup_Good_RegistersSessionActions(t *testing.T) {
assert.True(t, c.Action("issue.comment").Exists())
assert.True(t, c.Action("issue.report").Exists())
assert.True(t, c.Action("issue.archive").Exists())
assert.True(t, c.Action("agentic.message.send").Exists())
assert.True(t, c.Action("agent.message.send").Exists())
assert.True(t, c.Action("agentic.message.inbox").Exists())
assert.True(t, c.Action("agent.message.inbox").Exists())
assert.True(t, c.Action("agentic.message.conversation").Exists())
assert.True(t, c.Action("agent.message.conversation").Exists())
assert.True(t, c.Action("agentic.issue.update").Exists())
assert.True(t, c.Action("agentic.issue.assign").Exists())
assert.True(t, c.Action("agentic.issue.comment").Exists())
@ -606,6 +612,12 @@ func TestPrep_OnStartup_Good_RegistersPlatformCommandAlias(t *testing.T) {
require.True(t, s.OnStartup(context.Background()).OK)
assert.Contains(t, c.Commands(), "auth/provision")
assert.Contains(t, c.Commands(), "auth/revoke")
assert.Contains(t, c.Commands(), "message/send")
assert.Contains(t, c.Commands(), "messages/send")
assert.Contains(t, c.Commands(), "message/inbox")
assert.Contains(t, c.Commands(), "messages/inbox")
assert.Contains(t, c.Commands(), "message/conversation")
assert.Contains(t, c.Commands(), "messages/conversation")
assert.Contains(t, c.Commands(), "subscription/budget/update")
assert.Contains(t, c.Commands(), "subscription/update-budget")
assert.Contains(t, c.Commands(), "fleet/events")
@ -641,6 +653,9 @@ func TestPrep_RegisterTools_Good_RegistersCompletionTool(t *testing.T) {
}
assert.Contains(t, toolNames, "agentic_complete")
assert.Contains(t, toolNames, "agentic_message_send")
assert.Contains(t, toolNames, "agentic_message_inbox")
assert.Contains(t, toolNames, "agentic_message_conversation")
}
func TestPrep_OnStartup_Good_RegistersGenerateCommand(t *testing.T) {