fix(agentctl): register MCP tools with _kandev suffix to match sysprompt (#572)
This commit is contained in:
@@ -408,7 +408,7 @@ func scenarioClarification(e *emitter) {
|
||||
fixedDelay(100)
|
||||
e.text("Let me ask you a question about the project setup.")
|
||||
|
||||
result, err := callMCPTool("kandev", "ask_user_question", clarificationQuestionArgs())
|
||||
result, err := callMCPTool("kandev", "ask_user_question_kandev", clarificationQuestionArgs())
|
||||
if err != nil {
|
||||
e.text(fmt.Sprintf("Question failed: %s", err))
|
||||
return
|
||||
@@ -426,7 +426,7 @@ func scenarioClarificationTimeout(e *emitter) {
|
||||
ctx, cancel := contextWithTimeout(5)
|
||||
defer cancel()
|
||||
|
||||
result, err := callMCPToolCtx(ctx, "kandev", "ask_user_question", clarificationQuestionArgs())
|
||||
result, err := callMCPToolCtx(ctx, "kandev", "ask_user_question_kandev", clarificationQuestionArgs())
|
||||
if err != nil {
|
||||
fixedDelay(50)
|
||||
if ctx.Err() != nil {
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestIsScriptMode(t *testing.T) {
|
||||
{"e2e:message(\"hello\")", true},
|
||||
{"e2e:thinking(\"thought\")", true},
|
||||
{"e2e:delay(100)", true},
|
||||
{"e2e:mcp:kandev:create_task_plan({})", true},
|
||||
{"e2e:mcp:kandev:create_task_plan_kandev({})", true},
|
||||
{"/e2e:simple-message", false},
|
||||
{"hello world", false},
|
||||
{"", false},
|
||||
@@ -155,7 +155,7 @@ func TestSubstituteContextPlaceholders(t *testing.T) {
|
||||
Kandev Task ID: task-abc-123
|
||||
Kandev Session ID: sess-xyz-789
|
||||
</kandev-system>
|
||||
e2e:mcp:kandev:create_task_plan({"task_id":"{task_id}"})`
|
||||
e2e:mcp:kandev:create_task_plan_kandev({"task_id":"{task_id}"})`
|
||||
|
||||
t.Run("replaces task_id", func(t *testing.T) {
|
||||
args := map[string]any{"task_id": "{task_id}"}
|
||||
|
||||
@@ -13,57 +13,57 @@ import (
|
||||
|
||||
func (s *Server) registerConfigWorkflowTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewToolWithRawSchema("list_workspaces",
|
||||
mcp.NewToolWithRawSchema("list_workspaces_kandev",
|
||||
"List all workspaces. Use this first to get workspace IDs.",
|
||||
json.RawMessage(`{"type":"object","properties":{}}`),
|
||||
),
|
||||
s.wrapHandler("list_workspaces", s.listWorkspacesHandler()),
|
||||
s.wrapHandler("list_workspaces_kandev", s.listWorkspacesHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_workflows",
|
||||
mcp.NewTool("list_workflows_kandev",
|
||||
mcp.WithDescription("List all workflows in a workspace."),
|
||||
mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")),
|
||||
),
|
||||
s.wrapHandler("list_workflows", s.listWorkflowsHandler()),
|
||||
s.wrapHandler("list_workflows_kandev", s.listWorkflowsHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("create_workflow",
|
||||
mcp.NewTool("create_workflow_kandev",
|
||||
mcp.WithDescription("Create a new workflow in a workspace."),
|
||||
mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")),
|
||||
mcp.WithString("name", mcp.Required(), mcp.Description("Workflow name")),
|
||||
mcp.WithString("description", mcp.Description("Workflow description")),
|
||||
),
|
||||
s.wrapHandler("create_workflow", s.createWorkflowHandler()),
|
||||
s.wrapHandler("create_workflow_kandev", s.createWorkflowHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_workflow",
|
||||
mcp.NewTool("update_workflow_kandev",
|
||||
mcp.WithDescription("Update an existing workflow."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
|
||||
mcp.WithString("name", mcp.Description("New workflow name")),
|
||||
mcp.WithString("description", mcp.Description("New workflow description")),
|
||||
),
|
||||
s.wrapHandler("update_workflow", s.updateWorkflowHandler()),
|
||||
s.wrapHandler("update_workflow_kandev", s.updateWorkflowHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("delete_workflow",
|
||||
mcp.NewTool("delete_workflow_kandev",
|
||||
mcp.WithDescription("Delete a workflow and all its steps. This is destructive and cannot be undone."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID to delete")),
|
||||
),
|
||||
s.wrapHandler("delete_workflow", s.deleteWorkflowHandler()),
|
||||
s.wrapHandler("delete_workflow_kandev", s.deleteWorkflowHandler()),
|
||||
)
|
||||
s.registerConfigWorkflowStepTools()
|
||||
}
|
||||
|
||||
func (s *Server) registerConfigWorkflowStepTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_workflow_steps",
|
||||
mcp.NewTool("list_workflow_steps_kandev",
|
||||
mcp.WithDescription("List all workflow steps (columns) in a workflow."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
|
||||
),
|
||||
s.wrapHandler("list_workflow_steps", s.listWorkflowStepsHandler()),
|
||||
s.wrapHandler("list_workflow_steps_kandev", s.listWorkflowStepsHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("create_workflow_step",
|
||||
mcp.NewTool("create_workflow_step_kandev",
|
||||
mcp.WithDescription("Create a new workflow step (column) in a workflow."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
|
||||
mcp.WithString("name", mcp.Required(), mcp.Description("Step name")),
|
||||
@@ -75,10 +75,10 @@ func (s *Server) registerConfigWorkflowStepTools() {
|
||||
mcp.WithBoolean("show_in_command_panel", mcp.Description("Show this step in the command panel")),
|
||||
mcp.WithObject("events", mcp.Description("Event-driven actions. Keys: on_enter, on_exit, on_turn_start, on_turn_complete. Each is an array of {type, config} objects.")),
|
||||
),
|
||||
s.wrapHandler("create_workflow_step", s.createWorkflowStepHandler()),
|
||||
s.wrapHandler("create_workflow_step_kandev", s.createWorkflowStepHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_workflow_step",
|
||||
mcp.NewTool("update_workflow_step_kandev",
|
||||
mcp.WithDescription("Update an existing workflow step."),
|
||||
mcp.WithString("step_id", mcp.Required(), mcp.Description("The workflow step ID")),
|
||||
mcp.WithString("name", mcp.Description("New step name")),
|
||||
@@ -90,22 +90,22 @@ func (s *Server) registerConfigWorkflowStepTools() {
|
||||
mcp.WithNumber("auto_archive_after_hours", mcp.Description("Auto-archive tasks after N hours in this step (0 to disable)")),
|
||||
mcp.WithObject("events", mcp.Description("Event-driven actions. Keys: on_enter, on_exit, on_turn_start, on_turn_complete.")),
|
||||
),
|
||||
s.wrapHandler("update_workflow_step", s.updateWorkflowStepHandler()),
|
||||
s.wrapHandler("update_workflow_step_kandev", s.updateWorkflowStepHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("delete_workflow_step",
|
||||
mcp.NewTool("delete_workflow_step_kandev",
|
||||
mcp.WithDescription("Delete a workflow step. This is destructive and cannot be undone."),
|
||||
mcp.WithString("step_id", mcp.Required(), mcp.Description("The workflow step ID to delete")),
|
||||
),
|
||||
s.wrapHandler("delete_workflow_step", s.deleteWorkflowStepHandler()),
|
||||
s.wrapHandler("delete_workflow_step_kandev", s.deleteWorkflowStepHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("reorder_workflow_steps",
|
||||
mcp.NewTool("reorder_workflow_steps_kandev",
|
||||
mcp.WithDescription("Reorder workflow steps by providing the full ordered list of step IDs."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
|
||||
mcp.WithArray("step_ids", mcp.Required(), mcp.Description("Ordered list of step IDs defining the new order")),
|
||||
),
|
||||
s.wrapHandler("reorder_workflow_steps", s.reorderWorkflowStepsHandler()),
|
||||
s.wrapHandler("reorder_workflow_steps_kandev", s.reorderWorkflowStepsHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -113,37 +113,37 @@ func (s *Server) registerConfigWorkflowStepTools() {
|
||||
|
||||
func (s *Server) registerConfigAgentTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewToolWithRawSchema("list_agents",
|
||||
mcp.NewToolWithRawSchema("list_agents_kandev",
|
||||
"List all configured agents with their profiles.",
|
||||
json.RawMessage(`{"type":"object","properties":{}}`),
|
||||
),
|
||||
s.wrapHandler("list_agents", s.listAgentsHandler()),
|
||||
s.wrapHandler("list_agents_kandev", s.listAgentsHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_agent",
|
||||
mcp.NewTool("update_agent_kandev",
|
||||
mcp.WithDescription("Update an existing agent."),
|
||||
mcp.WithString("agent_id", mcp.Required(), mcp.Description("The agent ID")),
|
||||
mcp.WithBoolean("supports_mcp", mcp.Description("Whether the agent supports MCP")),
|
||||
mcp.WithString("mcp_config_path", mcp.Description("Path to MCP config file")),
|
||||
),
|
||||
s.wrapHandler("update_agent", s.updateAgentHandler()),
|
||||
s.wrapHandler("update_agent_kandev", s.updateAgentHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("create_agent_profile",
|
||||
mcp.NewTool("create_agent_profile_kandev",
|
||||
mcp.WithDescription("Create a new agent profile for an agent."),
|
||||
mcp.WithString("agent_id", mcp.Required(), mcp.Description("The agent ID to create a profile for")),
|
||||
mcp.WithString("name", mcp.Required(), mcp.Description("Profile name")),
|
||||
mcp.WithString("model", mcp.Required(), mcp.Description("Model name (e.g. 'claude-sonnet-4-5-20250514')")),
|
||||
mcp.WithBoolean("auto_approve", mcp.Description("Auto-approve permissions (default: false)")),
|
||||
),
|
||||
s.wrapHandler("create_agent_profile", s.createAgentProfileHandler()),
|
||||
s.wrapHandler("create_agent_profile_kandev", s.createAgentProfileHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("delete_agent_profile",
|
||||
mcp.NewTool("delete_agent_profile_kandev",
|
||||
mcp.WithDescription("Delete an agent profile. Fails if the profile is used by an active session."),
|
||||
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The profile ID to delete")),
|
||||
),
|
||||
s.wrapHandler("delete_agent_profile", s.deleteAgentProfileHandler()),
|
||||
s.wrapHandler("delete_agent_profile_kandev", s.deleteAgentProfileHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,37 +151,37 @@ func (s *Server) registerConfigAgentTools() {
|
||||
|
||||
func (s *Server) registerConfigMcpTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_agent_profiles",
|
||||
mcp.NewTool("list_agent_profiles_kandev",
|
||||
mcp.WithDescription("List all profiles for an agent."),
|
||||
mcp.WithString("agent_id", mcp.Required(), mcp.Description("The agent ID")),
|
||||
),
|
||||
s.wrapHandler("list_agent_profiles", s.listAgentProfilesHandler()),
|
||||
s.wrapHandler("list_agent_profiles_kandev", s.listAgentProfilesHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_agent_profile",
|
||||
mcp.NewTool("update_agent_profile_kandev",
|
||||
mcp.WithDescription("Update an agent profile's settings."),
|
||||
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The profile ID")),
|
||||
mcp.WithString("name", mcp.Description("New profile name")),
|
||||
mcp.WithString("model", mcp.Description("New model name")),
|
||||
mcp.WithBoolean("auto_approve", mcp.Description("Auto-approve permissions")),
|
||||
),
|
||||
s.wrapHandler("update_agent_profile", s.updateAgentProfileHandler()),
|
||||
s.wrapHandler("update_agent_profile_kandev", s.updateAgentProfileHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("get_mcp_config",
|
||||
mcp.NewTool("get_mcp_config_kandev",
|
||||
mcp.WithDescription("Get MCP server configuration for an agent profile."),
|
||||
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The agent profile ID")),
|
||||
),
|
||||
s.wrapHandler("get_mcp_config", s.getMcpConfigHandler()),
|
||||
s.wrapHandler("get_mcp_config_kandev", s.getMcpConfigHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_mcp_config",
|
||||
mcp.NewTool("update_mcp_config_kandev",
|
||||
mcp.WithDescription("Update MCP server configuration for an agent profile. Pass the full servers map."),
|
||||
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The agent profile ID")),
|
||||
mcp.WithBoolean("enabled", mcp.Description("Whether MCP is enabled for this profile")),
|
||||
mcp.WithObject("servers", mcp.Description("Full MCP servers map to set. Each key is a server name, value is the server configuration object.")),
|
||||
),
|
||||
s.wrapHandler("update_mcp_config", s.updateMcpConfigHandler()),
|
||||
s.wrapHandler("update_mcp_config_kandev", s.updateMcpConfigHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -189,21 +189,21 @@ func (s *Server) registerConfigMcpTools() {
|
||||
|
||||
func (s *Server) registerConfigExecutorTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewToolWithRawSchema("list_executors",
|
||||
mcp.NewToolWithRawSchema("list_executors_kandev",
|
||||
"List all executors.",
|
||||
json.RawMessage(`{"type":"object","properties":{}}`),
|
||||
),
|
||||
s.wrapHandler("list_executors", s.listExecutorsHandler()),
|
||||
s.wrapHandler("list_executors_kandev", s.listExecutorsHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_executor_profiles",
|
||||
mcp.NewTool("list_executor_profiles_kandev",
|
||||
mcp.WithDescription("List all profiles for an executor."),
|
||||
mcp.WithString("executor_id", mcp.Required(), mcp.Description("The executor ID")),
|
||||
),
|
||||
s.wrapHandler("list_executor_profiles", s.listExecutorProfilesHandler()),
|
||||
s.wrapHandler("list_executor_profiles_kandev", s.listExecutorProfilesHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("create_executor_profile",
|
||||
mcp.NewTool("create_executor_profile_kandev",
|
||||
mcp.WithDescription("Create a new executor profile."),
|
||||
mcp.WithString("executor_id", mcp.Required(), mcp.Description("The executor ID")),
|
||||
mcp.WithString("name", mcp.Required(), mcp.Description("Profile name")),
|
||||
@@ -212,10 +212,10 @@ func (s *Server) registerConfigExecutorTools() {
|
||||
mcp.WithString("prepare_script", mcp.Description("Script to run before agent starts")),
|
||||
mcp.WithString("cleanup_script", mcp.Description("Script to run after agent stops")),
|
||||
),
|
||||
s.wrapHandler("create_executor_profile", s.createExecutorProfileHandler()),
|
||||
s.wrapHandler("create_executor_profile_kandev", s.createExecutorProfileHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_executor_profile",
|
||||
mcp.NewTool("update_executor_profile_kandev",
|
||||
mcp.WithDescription("Update an existing executor profile."),
|
||||
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The executor profile ID")),
|
||||
mcp.WithString("name", mcp.Description("New profile name")),
|
||||
@@ -224,14 +224,14 @@ func (s *Server) registerConfigExecutorTools() {
|
||||
mcp.WithString("prepare_script", mcp.Description("New prepare script")),
|
||||
mcp.WithString("cleanup_script", mcp.Description("New cleanup script")),
|
||||
),
|
||||
s.wrapHandler("update_executor_profile", s.updateExecutorProfileHandler()),
|
||||
s.wrapHandler("update_executor_profile_kandev", s.updateExecutorProfileHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("delete_executor_profile",
|
||||
mcp.NewTool("delete_executor_profile_kandev",
|
||||
mcp.WithDescription("Delete an executor profile."),
|
||||
mcp.WithString("profile_id", mcp.Required(), mcp.Description("The executor profile ID to delete")),
|
||||
),
|
||||
s.wrapHandler("delete_executor_profile", s.deleteExecutorProfileHandler()),
|
||||
s.wrapHandler("delete_executor_profile_kandev", s.deleteExecutorProfileHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -239,43 +239,43 @@ func (s *Server) registerConfigExecutorTools() {
|
||||
|
||||
func (s *Server) registerConfigTaskTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_tasks",
|
||||
mcp.NewTool("list_tasks_kandev",
|
||||
mcp.WithDescription("List all tasks in a workflow."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
|
||||
),
|
||||
s.wrapHandler("list_tasks", s.listTasksHandler()),
|
||||
s.wrapHandler("list_tasks_kandev", s.listTasksHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("move_task",
|
||||
mcp.NewTool("move_task_kandev",
|
||||
mcp.WithDescription("Move a task to a different workflow step."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID")),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("Target workflow ID")),
|
||||
mcp.WithString("workflow_step_id", mcp.Required(), mcp.Description("Target workflow step ID")),
|
||||
mcp.WithNumber("position", mcp.Description("Position within the step (0-based)")),
|
||||
),
|
||||
s.wrapHandler("move_task", s.moveTaskHandler()),
|
||||
s.wrapHandler("move_task_kandev", s.moveTaskHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("delete_task",
|
||||
mcp.NewTool("delete_task_kandev",
|
||||
mcp.WithDescription("Delete a task permanently."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to delete")),
|
||||
),
|
||||
s.wrapHandler("delete_task", s.deleteTaskHandler()),
|
||||
s.wrapHandler("delete_task_kandev", s.deleteTaskHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("archive_task",
|
||||
mcp.NewTool("archive_task_kandev",
|
||||
mcp.WithDescription("Archive a task."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to archive")),
|
||||
),
|
||||
s.wrapHandler("archive_task", s.archiveTaskHandler()),
|
||||
s.wrapHandler("archive_task_kandev", s.archiveTaskHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_task_state",
|
||||
mcp.NewTool("update_task_state_kandev",
|
||||
mcp.WithDescription("Update the state of a task."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID")),
|
||||
mcp.WithString("state", mcp.Required(), mcp.Description("New state: open, in_progress, complete, blocked, cancelled")),
|
||||
),
|
||||
s.wrapHandler("update_task_state", s.updateTaskStateHandler()),
|
||||
s.wrapHandler("update_task_state_kandev", s.updateTaskStateHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestCreateWorkflowHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_workflow", map[string]interface{}{
|
||||
result := callTool(t, s, "create_workflow_kandev", map[string]interface{}{
|
||||
"workspace_id": "ws-123",
|
||||
"name": "Sprint Board",
|
||||
"description": "A sprint workflow",
|
||||
@@ -118,7 +118,7 @@ func TestCreateWorkflowHandler_MissingWorkspaceID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_workflow", map[string]interface{}{
|
||||
result := callTool(t, s, "create_workflow_kandev", map[string]interface{}{
|
||||
"name": "Sprint Board",
|
||||
})
|
||||
|
||||
@@ -129,7 +129,7 @@ func TestCreateWorkflowHandler_MissingName(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_workflow", map[string]interface{}{
|
||||
result := callTool(t, s, "create_workflow_kandev", map[string]interface{}{
|
||||
"workspace_id": "ws-123",
|
||||
})
|
||||
|
||||
@@ -142,7 +142,7 @@ func TestUpdateWorkflowHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_workflow", map[string]interface{}{
|
||||
result := callTool(t, s, "update_workflow_kandev", map[string]interface{}{
|
||||
"workflow_id": "wf-1",
|
||||
"name": "Updated",
|
||||
"description": "New description",
|
||||
@@ -156,7 +156,7 @@ func TestUpdateWorkflowHandler_MissingWorkflowID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_workflow", map[string]interface{}{
|
||||
result := callTool(t, s, "update_workflow_kandev", map[string]interface{}{
|
||||
"name": "Updated",
|
||||
})
|
||||
|
||||
@@ -169,7 +169,7 @@ func TestDeleteWorkflowHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "delete_workflow", map[string]interface{}{
|
||||
result := callTool(t, s, "delete_workflow_kandev", map[string]interface{}{
|
||||
"workflow_id": "wf-1",
|
||||
})
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestDeleteWorkflowHandler_MissingWorkflowID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "delete_workflow", map[string]interface{}{})
|
||||
result := callTool(t, s, "delete_workflow_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -192,7 +192,7 @@ func TestCreateWorkflowStepHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
|
||||
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
|
||||
"workflow_id": "wf-123",
|
||||
"name": "Review",
|
||||
"color": "#3b82f6",
|
||||
@@ -209,7 +209,7 @@ func TestCreateWorkflowStepHandler_AllFields(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
|
||||
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
|
||||
"workflow_id": "wf-123",
|
||||
"name": "Deploy",
|
||||
"position": float64(0),
|
||||
@@ -237,7 +237,7 @@ func TestCreateWorkflowStepHandler_MissingWorkflowID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
|
||||
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
|
||||
"name": "Review",
|
||||
})
|
||||
|
||||
@@ -248,7 +248,7 @@ func TestCreateWorkflowStepHandler_MissingName(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_workflow_step", map[string]interface{}{
|
||||
result := callTool(t, s, "create_workflow_step_kandev", map[string]interface{}{
|
||||
"workflow_id": "wf-123",
|
||||
})
|
||||
|
||||
@@ -261,7 +261,7 @@ func TestUpdateWorkflowStepHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_workflow_step", map[string]interface{}{
|
||||
result := callTool(t, s, "update_workflow_step_kandev", map[string]interface{}{
|
||||
"step_id": "step-1",
|
||||
"name": "Updated Name",
|
||||
})
|
||||
@@ -276,7 +276,7 @@ func TestUpdateWorkflowStepHandler_AllFields(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_workflow_step", map[string]interface{}{
|
||||
result := callTool(t, s, "update_workflow_step_kandev", map[string]interface{}{
|
||||
"step_id": "step-1",
|
||||
"name": "In Review",
|
||||
"color": "#3b82f6",
|
||||
@@ -302,7 +302,7 @@ func TestUpdateWorkflowStepHandler_MissingStepID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_workflow_step", map[string]interface{}{
|
||||
result := callTool(t, s, "update_workflow_step_kandev", map[string]interface{}{
|
||||
"name": "Updated",
|
||||
})
|
||||
|
||||
@@ -317,7 +317,7 @@ func TestListAgentsHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_agents", map[string]interface{}{})
|
||||
result := callTool(t, s, "list_agents_kandev", map[string]interface{}{})
|
||||
|
||||
assert.False(t, result.IsError)
|
||||
assert.Equal(t, ws.ActionMCPListAgents, backend.lastAction)
|
||||
@@ -329,7 +329,7 @@ func TestCreateAgentProfileHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_agent_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "create_agent_profile_kandev", map[string]interface{}{
|
||||
"agent_id": "agent-1",
|
||||
"name": "My Profile",
|
||||
"model": "claude-sonnet-4-5-20250514",
|
||||
@@ -343,7 +343,7 @@ func TestCreateAgentProfileHandler_MissingAgentID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_agent_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "create_agent_profile_kandev", map[string]interface{}{
|
||||
"name": "My Profile",
|
||||
"model": "claude-sonnet-4-5-20250514",
|
||||
})
|
||||
@@ -355,7 +355,7 @@ func TestCreateAgentProfileHandler_MissingModel(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_agent_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "create_agent_profile_kandev", map[string]interface{}{
|
||||
"agent_id": "agent-1",
|
||||
"name": "My Profile",
|
||||
})
|
||||
@@ -369,7 +369,7 @@ func TestUpdateAgentHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_agent", map[string]interface{}{
|
||||
result := callTool(t, s, "update_agent_kandev", map[string]interface{}{
|
||||
"agent_id": "agent-1",
|
||||
"supports_mcp": true,
|
||||
})
|
||||
@@ -382,7 +382,7 @@ func TestUpdateAgentHandler_MissingAgentID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_agent", map[string]interface{}{
|
||||
result := callTool(t, s, "update_agent_kandev", map[string]interface{}{
|
||||
"supports_mcp": true,
|
||||
})
|
||||
|
||||
@@ -395,7 +395,7 @@ func TestDeleteAgentProfileHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "delete_agent_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "delete_agent_profile_kandev", map[string]interface{}{
|
||||
"profile_id": "profile-1",
|
||||
})
|
||||
|
||||
@@ -407,7 +407,7 @@ func TestDeleteAgentProfileHandler_MissingProfileID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "delete_agent_profile", map[string]interface{}{})
|
||||
result := callTool(t, s, "delete_agent_profile_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -420,7 +420,7 @@ func TestListAgentProfilesHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_agent_profiles", map[string]interface{}{
|
||||
result := callTool(t, s, "list_agent_profiles_kandev", map[string]interface{}{
|
||||
"agent_id": "agent-1",
|
||||
})
|
||||
|
||||
@@ -432,7 +432,7 @@ func TestListAgentProfilesHandler_MissingAgentID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_agent_profiles", map[string]interface{}{})
|
||||
result := callTool(t, s, "list_agent_profiles_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -443,7 +443,7 @@ func TestUpdateAgentProfileHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_agent_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "update_agent_profile_kandev", map[string]interface{}{
|
||||
"profile_id": "profile-1",
|
||||
"name": "Updated Profile",
|
||||
"model": "claude-3.5-sonnet",
|
||||
@@ -457,7 +457,7 @@ func TestUpdateAgentProfileHandler_MissingProfileID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_agent_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "update_agent_profile_kandev", map[string]interface{}{
|
||||
"name": "Updated",
|
||||
})
|
||||
|
||||
@@ -470,7 +470,7 @@ func TestGetMcpConfigHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "get_mcp_config", map[string]interface{}{
|
||||
result := callTool(t, s, "get_mcp_config_kandev", map[string]interface{}{
|
||||
"profile_id": "p-1",
|
||||
})
|
||||
|
||||
@@ -482,7 +482,7 @@ func TestGetMcpConfigHandler_MissingProfileID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "get_mcp_config", map[string]interface{}{})
|
||||
result := callTool(t, s, "get_mcp_config_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -493,7 +493,7 @@ func TestUpdateMcpConfigHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_mcp_config", map[string]interface{}{
|
||||
result := callTool(t, s, "update_mcp_config_kandev", map[string]interface{}{
|
||||
"profile_id": "p-1",
|
||||
"enabled": true,
|
||||
})
|
||||
@@ -506,7 +506,7 @@ func TestUpdateMcpConfigHandler_MissingProfileID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_mcp_config", map[string]interface{}{})
|
||||
result := callTool(t, s, "update_mcp_config_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -519,7 +519,7 @@ func TestListExecutorsHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_executors", map[string]interface{}{})
|
||||
result := callTool(t, s, "list_executors_kandev", map[string]interface{}{})
|
||||
|
||||
assert.False(t, result.IsError)
|
||||
assert.Equal(t, ws.ActionMCPListExecutors, backend.lastAction)
|
||||
@@ -531,7 +531,7 @@ func TestListExecutorProfilesHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_executor_profiles", map[string]interface{}{
|
||||
result := callTool(t, s, "list_executor_profiles_kandev", map[string]interface{}{
|
||||
"executor_id": "exec-1",
|
||||
})
|
||||
|
||||
@@ -543,7 +543,7 @@ func TestListExecutorProfilesHandler_MissingExecutorID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_executor_profiles", map[string]interface{}{})
|
||||
result := callTool(t, s, "list_executor_profiles_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -554,7 +554,7 @@ func TestCreateExecutorProfileHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_executor_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "create_executor_profile_kandev", map[string]interface{}{
|
||||
"executor_id": "exec-1",
|
||||
"name": "Default",
|
||||
})
|
||||
@@ -571,7 +571,7 @@ func TestCreateExecutorProfileHandler_MissingExecutorID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_executor_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "create_executor_profile_kandev", map[string]interface{}{
|
||||
"name": "Default",
|
||||
})
|
||||
|
||||
@@ -582,7 +582,7 @@ func TestCreateExecutorProfileHandler_MissingName(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "create_executor_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "create_executor_profile_kandev", map[string]interface{}{
|
||||
"executor_id": "exec-1",
|
||||
})
|
||||
|
||||
@@ -595,7 +595,7 @@ func TestUpdateExecutorProfileHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_executor_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "update_executor_profile_kandev", map[string]interface{}{
|
||||
"profile_id": "prof-1",
|
||||
"name": "Updated Profile",
|
||||
})
|
||||
@@ -608,7 +608,7 @@ func TestUpdateExecutorProfileHandler_MissingProfileID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "update_executor_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "update_executor_profile_kandev", map[string]interface{}{
|
||||
"name": "Updated",
|
||||
})
|
||||
|
||||
@@ -621,7 +621,7 @@ func TestDeleteExecutorProfileHandler_Success(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "delete_executor_profile", map[string]interface{}{
|
||||
result := callTool(t, s, "delete_executor_profile_kandev", map[string]interface{}{
|
||||
"profile_id": "prof-1",
|
||||
})
|
||||
|
||||
@@ -633,7 +633,7 @@ func TestDeleteExecutorProfileHandler_MissingProfileID(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "delete_executor_profile", map[string]interface{}{})
|
||||
result := callTool(t, s, "delete_executor_profile_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -646,7 +646,7 @@ func TestForwardToBackend_BackendError(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_agents", map[string]interface{}{})
|
||||
result := callTool(t, s, "list_agents_kandev", map[string]interface{}{})
|
||||
|
||||
assert.True(t, result.IsError)
|
||||
}
|
||||
@@ -662,7 +662,7 @@ func TestForwardToBackend_ResultContainsJSON(t *testing.T) {
|
||||
}
|
||||
s := newTestServer(t, backend)
|
||||
|
||||
result := callTool(t, s, "list_agents", map[string]interface{}{})
|
||||
result := callTool(t, s, "list_agents_kandev", map[string]interface{}{})
|
||||
|
||||
assert.False(t, result.IsError)
|
||||
require.NotEmpty(t, result.Content)
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestCreateTask_ToolSchema_HasParentID(t *testing.T) {
|
||||
s := newTaskModeServer(t, backend, "task-current")
|
||||
|
||||
toolsMap := s.mcpServer.ListTools()
|
||||
tool, ok := toolsMap["create_task"]
|
||||
tool, ok := toolsMap["create_task_kandev"]
|
||||
require.True(t, ok, "create_task tool not registered")
|
||||
|
||||
schema, err := json.Marshal(tool.Tool.InputSchema)
|
||||
@@ -54,7 +54,7 @@ func TestCreateTask_SelfResolvesToTaskID(t *testing.T) {
|
||||
}
|
||||
s := newTaskModeServer(t, backend, "task-current")
|
||||
|
||||
result := callTool(t, s, "create_task", map[string]interface{}{
|
||||
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
|
||||
"title": "Write tests",
|
||||
"parent_id": "self",
|
||||
})
|
||||
@@ -72,7 +72,7 @@ func TestCreateTask_SelfWithNoTaskContext_ReturnsError(t *testing.T) {
|
||||
backend := &testBackend{}
|
||||
s := newTaskModeServer(t, backend, "")
|
||||
|
||||
result := callTool(t, s, "create_task", map[string]interface{}{
|
||||
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
|
||||
"title": "Write tests",
|
||||
"parent_id": "self",
|
||||
})
|
||||
@@ -86,7 +86,7 @@ func TestCreateTask_ExplicitParentID(t *testing.T) {
|
||||
}
|
||||
s := newTaskModeServer(t, backend, "task-current")
|
||||
|
||||
result := callTool(t, s, "create_task", map[string]interface{}{
|
||||
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
|
||||
"title": "Fix bug",
|
||||
"parent_id": "task-abc",
|
||||
})
|
||||
@@ -103,7 +103,7 @@ func TestCreateTask_NoParentID_RequiresWorkspaceAndWorkflow(t *testing.T) {
|
||||
s := newTaskModeServer(t, backend, "task-current")
|
||||
|
||||
// No parent_id and no workspace/workflow -> error
|
||||
result := callTool(t, s, "create_task", map[string]interface{}{
|
||||
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
|
||||
"title": "Standalone task",
|
||||
})
|
||||
|
||||
@@ -116,7 +116,7 @@ func TestCreateTask_NoParentID_WithIDs_CreatesTopLevelTask(t *testing.T) {
|
||||
}
|
||||
s := newTaskModeServer(t, backend, "task-current")
|
||||
|
||||
result := callTool(t, s, "create_task", map[string]interface{}{
|
||||
result := callTool(t, s, "create_task_kandev", map[string]interface{}{
|
||||
"title": "Standalone",
|
||||
"workspace_id": "ws-1",
|
||||
"workflow_id": "wf-1",
|
||||
|
||||
@@ -262,36 +262,36 @@ func (s *Server) registerKanbanTools() {
|
||||
// omitempty which drops empty properties maps, causing OpenAI API validation
|
||||
// errors ("object schema missing properties").
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewToolWithRawSchema("list_workspaces",
|
||||
mcp.NewToolWithRawSchema("list_workspaces_kandev",
|
||||
"List all workspaces. Use this first to get workspace IDs.",
|
||||
json.RawMessage(`{"type":"object","properties":{}}`),
|
||||
),
|
||||
s.wrapHandler("list_workspaces", s.listWorkspacesHandler()),
|
||||
s.wrapHandler("list_workspaces_kandev", s.listWorkspacesHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_workflows",
|
||||
mcp.NewTool("list_workflows_kandev",
|
||||
mcp.WithDescription("List all workflows in a workspace."),
|
||||
mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")),
|
||||
),
|
||||
s.wrapHandler("list_workflows", s.listWorkflowsHandler()),
|
||||
s.wrapHandler("list_workflows_kandev", s.listWorkflowsHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_workflow_steps",
|
||||
mcp.NewTool("list_workflow_steps_kandev",
|
||||
mcp.WithDescription("List all workflow steps in a workflow."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
|
||||
),
|
||||
s.wrapHandler("list_workflow_steps", s.listWorkflowStepsHandler()),
|
||||
s.wrapHandler("list_workflow_steps_kandev", s.listWorkflowStepsHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_tasks",
|
||||
mcp.NewTool("list_tasks_kandev",
|
||||
mcp.WithDescription("List all tasks in a workflow."),
|
||||
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")),
|
||||
),
|
||||
s.wrapHandler("list_tasks", s.listTasksHandler()),
|
||||
s.wrapHandler("list_tasks_kandev", s.listTasksHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("create_task",
|
||||
mcp.WithDescription("Create a new task or subtask and auto-start an agent on it. For subtasks (parent_id='self'), the executor profile and agent profile are automatically inherited from the parent session — no need to ask. For top-level tasks, use ask_user_question first if you do not already know which executor profile and agent profile the user wants to use. IMPORTANT: 'description' is the initial prompt the sub-agent receives — it is the ONLY context the sub-agent has to start working. Always provide a detailed description for subtasks."),
|
||||
mcp.NewTool("create_task_kandev",
|
||||
mcp.WithDescription("Create a new task or subtask and auto-start an agent on it. For subtasks (parent_id='self'), the executor profile and agent profile are automatically inherited from the parent session — no need to ask. For top-level tasks, use ask_user_question_kandev first if you do not already know which executor profile and agent profile the user wants to use. IMPORTANT: 'description' is the initial prompt the sub-agent receives — it is the ONLY context the sub-agent has to start working. Always provide a detailed description for subtasks."),
|
||||
mcp.WithString("parent_id", mcp.Description("Parent task ID for subtasks. Use 'self' to create a subtask of your current task. Omit to create a top-level task.")),
|
||||
mcp.WithString("workspace_id", mcp.Description("The workspace ID (required for top-level tasks, inherited from parent for subtasks)")),
|
||||
mcp.WithString("workflow_id", mcp.Description("The workflow ID (required for top-level tasks, inherited from parent for subtasks)")),
|
||||
@@ -301,37 +301,37 @@ func (s *Server) registerKanbanTools() {
|
||||
mcp.WithString("agent_profile_id", mcp.Description("Agent profile ID to use. For subtasks, inherited from the parent session. For top-level tasks, ask the user which agent profile they want (e.g. Claude Code, OpenCode) if not already known.")),
|
||||
mcp.WithString("executor_profile_id", mcp.Description("Executor profile ID to use (determines the runtime environment: local, worktree, docker, etc.). For subtasks, inherited from the parent session. For top-level tasks, ask the user which executor profile they want if not already known.")),
|
||||
),
|
||||
s.wrapHandler("create_task", s.createTaskHandler()),
|
||||
s.wrapHandler("create_task_kandev", s.createTaskHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewToolWithRawSchema("list_agents",
|
||||
"List all configured agents with their profiles. Use this to find available agent_profile_ids for create_task.",
|
||||
mcp.NewToolWithRawSchema("list_agents_kandev",
|
||||
"List all configured agents with their profiles. Use this to find available agent_profile_ids for create_task_kandev.",
|
||||
json.RawMessage(`{"type":"object","properties":{}}`),
|
||||
),
|
||||
s.wrapHandler("list_agents", s.listAgentsHandler()),
|
||||
s.wrapHandler("list_agents_kandev", s.listAgentsHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("list_executor_profiles",
|
||||
mcp.WithDescription("List all profiles for an executor. Use this to find available executor_profile_ids for create_task. Standard executor IDs: exec-local (standalone process), exec-worktree (git worktree), exec-local-docker (Docker container), exec-sprites (cloud)."),
|
||||
mcp.NewTool("list_executor_profiles_kandev",
|
||||
mcp.WithDescription("List all profiles for an executor. Use this to find available executor_profile_ids for create_task_kandev. Standard executor IDs: exec-local (standalone process), exec-worktree (git worktree), exec-local-docker (Docker container), exec-sprites (cloud)."),
|
||||
mcp.WithString("executor_id", mcp.Required(), mcp.Description("The executor ID (e.g. exec-local, exec-worktree, exec-local-docker, exec-sprites)")),
|
||||
),
|
||||
s.wrapHandler("list_executor_profiles", s.listExecutorProfilesHandler()),
|
||||
s.wrapHandler("list_executor_profiles_kandev", s.listExecutorProfilesHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_task",
|
||||
mcp.NewTool("update_task_kandev",
|
||||
mcp.WithDescription("Update an existing task."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID")),
|
||||
mcp.WithString("title", mcp.Description("New title")),
|
||||
mcp.WithString("description", mcp.Description("New description")),
|
||||
mcp.WithString("state", mcp.Description("New state: not_started, in_progress, etc.")),
|
||||
),
|
||||
s.wrapHandler("update_task", s.updateTaskHandler()),
|
||||
s.wrapHandler("update_task_kandev", s.updateTaskHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) registerInteractionTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("ask_user_question",
|
||||
mcp.NewTool("ask_user_question_kandev",
|
||||
mcp.WithDescription(`Ask the user a clarifying question with multiple choice options.
|
||||
|
||||
Use this tool when you need user input to proceed. The user will see the prompt and can select one of the options or provide a custom text response.
|
||||
@@ -381,41 +381,41 @@ Another example:
|
||||
),
|
||||
mcp.WithString("context", mcp.Description("Optional background information to help the user understand why you're asking this question.")),
|
||||
),
|
||||
s.wrapHandler("ask_user_question", s.askUserQuestionHandler()),
|
||||
s.wrapHandler("ask_user_question_kandev", s.askUserQuestionHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) registerPlanTools() {
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("create_task_plan",
|
||||
mcp.NewTool("create_task_plan_kandev",
|
||||
mcp.WithDescription("Create or save a task plan. Use this to save your implementation plan for the current task."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to create a plan for")),
|
||||
mcp.WithString("content", mcp.Required(), mcp.Description("The plan content in markdown format")),
|
||||
mcp.WithString("title", mcp.Description("Optional title for the plan (default: 'Plan')")),
|
||||
),
|
||||
s.wrapHandler("create_task_plan", s.createTaskPlanHandler()),
|
||||
s.wrapHandler("create_task_plan_kandev", s.createTaskPlanHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("get_task_plan",
|
||||
mcp.NewTool("get_task_plan_kandev",
|
||||
mcp.WithDescription("Get the current plan for a task. Use this to retrieve an existing plan, including any user edits."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to get the plan for")),
|
||||
),
|
||||
s.wrapHandler("get_task_plan", s.getTaskPlanHandler()),
|
||||
s.wrapHandler("get_task_plan_kandev", s.getTaskPlanHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("update_task_plan",
|
||||
mcp.NewTool("update_task_plan_kandev",
|
||||
mcp.WithDescription("Update an existing task plan. Use this to modify the plan during implementation."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to update the plan for")),
|
||||
mcp.WithString("content", mcp.Required(), mcp.Description("The updated plan content in markdown format")),
|
||||
mcp.WithString("title", mcp.Description("Optional new title for the plan")),
|
||||
),
|
||||
s.wrapHandler("update_task_plan", s.updateTaskPlanHandler()),
|
||||
s.wrapHandler("update_task_plan_kandev", s.updateTaskPlanHandler()),
|
||||
)
|
||||
s.mcpServer.AddTool(
|
||||
mcp.NewTool("delete_task_plan",
|
||||
mcp.NewTool("delete_task_plan_kandev",
|
||||
mcp.WithDescription("Delete a task plan."),
|
||||
mcp.WithString("task_id", mcp.Required(), mcp.Description("The task ID to delete the plan for")),
|
||||
),
|
||||
s.wrapHandler("delete_task_plan", s.deleteTaskPlanHandler()),
|
||||
s.wrapHandler("delete_task_plan_kandev", s.deleteTaskPlanHandler()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,49 +36,49 @@ func TestServerModeTask_RegistersCorrectTools(t *testing.T) {
|
||||
tools := getRegisteredToolNames(s)
|
||||
|
||||
// Task mode should have kanban tools
|
||||
assert.Contains(t, tools, "list_workspaces")
|
||||
assert.Contains(t, tools, "list_workflows")
|
||||
assert.Contains(t, tools, "list_workflow_steps")
|
||||
assert.Contains(t, tools, "list_tasks")
|
||||
assert.Contains(t, tools, "create_task")
|
||||
assert.Contains(t, tools, "update_task")
|
||||
assert.Contains(t, tools, "list_workspaces_kandev")
|
||||
assert.Contains(t, tools, "list_workflows_kandev")
|
||||
assert.Contains(t, tools, "list_workflow_steps_kandev")
|
||||
assert.Contains(t, tools, "list_tasks_kandev")
|
||||
assert.Contains(t, tools, "create_task_kandev")
|
||||
assert.Contains(t, tools, "update_task_kandev")
|
||||
|
||||
// Task mode should have plan tools
|
||||
assert.Contains(t, tools, "create_task_plan")
|
||||
assert.Contains(t, tools, "get_task_plan")
|
||||
assert.Contains(t, tools, "update_task_plan")
|
||||
assert.Contains(t, tools, "delete_task_plan")
|
||||
assert.Contains(t, tools, "create_task_plan_kandev")
|
||||
assert.Contains(t, tools, "get_task_plan_kandev")
|
||||
assert.Contains(t, tools, "update_task_plan_kandev")
|
||||
assert.Contains(t, tools, "delete_task_plan_kandev")
|
||||
|
||||
// Task mode should have interaction tools
|
||||
assert.Contains(t, tools, "ask_user_question")
|
||||
assert.Contains(t, tools, "ask_user_question_kandev")
|
||||
|
||||
// Task mode should have profile listing tools (needed for create_task)
|
||||
assert.Contains(t, tools, "list_agents")
|
||||
assert.Contains(t, tools, "list_executor_profiles")
|
||||
assert.Contains(t, tools, "list_agents_kandev")
|
||||
assert.Contains(t, tools, "list_executor_profiles_kandev")
|
||||
|
||||
// Task mode should NOT have config/mutation tools
|
||||
assert.NotContains(t, tools, "create_workflow")
|
||||
assert.NotContains(t, tools, "update_workflow")
|
||||
assert.NotContains(t, tools, "delete_workflow")
|
||||
assert.NotContains(t, tools, "create_workflow_step")
|
||||
assert.NotContains(t, tools, "update_workflow_step")
|
||||
assert.NotContains(t, tools, "update_agent")
|
||||
assert.NotContains(t, tools, "create_agent_profile")
|
||||
assert.NotContains(t, tools, "delete_agent_profile")
|
||||
assert.NotContains(t, tools, "list_agent_profiles")
|
||||
assert.NotContains(t, tools, "update_agent_profile")
|
||||
assert.NotContains(t, tools, "get_mcp_config")
|
||||
assert.NotContains(t, tools, "update_mcp_config")
|
||||
assert.NotContains(t, tools, "move_task")
|
||||
assert.NotContains(t, tools, "delete_task")
|
||||
assert.NotContains(t, tools, "archive_task")
|
||||
assert.NotContains(t, tools, "list_executors")
|
||||
assert.NotContains(t, tools, "create_executor_profile")
|
||||
assert.NotContains(t, tools, "update_executor_profile")
|
||||
assert.NotContains(t, tools, "delete_executor_profile")
|
||||
assert.NotContains(t, tools, "update_task_state")
|
||||
assert.NotContains(t, tools, "delete_workflow_step")
|
||||
assert.NotContains(t, tools, "reorder_workflow_steps")
|
||||
assert.NotContains(t, tools, "create_workflow_kandev")
|
||||
assert.NotContains(t, tools, "update_workflow_kandev")
|
||||
assert.NotContains(t, tools, "delete_workflow_kandev")
|
||||
assert.NotContains(t, tools, "create_workflow_step_kandev")
|
||||
assert.NotContains(t, tools, "update_workflow_step_kandev")
|
||||
assert.NotContains(t, tools, "update_agent_kandev")
|
||||
assert.NotContains(t, tools, "create_agent_profile_kandev")
|
||||
assert.NotContains(t, tools, "delete_agent_profile_kandev")
|
||||
assert.NotContains(t, tools, "list_agent_profiles_kandev")
|
||||
assert.NotContains(t, tools, "update_agent_profile_kandev")
|
||||
assert.NotContains(t, tools, "get_mcp_config_kandev")
|
||||
assert.NotContains(t, tools, "update_mcp_config_kandev")
|
||||
assert.NotContains(t, tools, "move_task_kandev")
|
||||
assert.NotContains(t, tools, "delete_task_kandev")
|
||||
assert.NotContains(t, tools, "archive_task_kandev")
|
||||
assert.NotContains(t, tools, "list_executors_kandev")
|
||||
assert.NotContains(t, tools, "create_executor_profile_kandev")
|
||||
assert.NotContains(t, tools, "update_executor_profile_kandev")
|
||||
assert.NotContains(t, tools, "delete_executor_profile_kandev")
|
||||
assert.NotContains(t, tools, "update_task_state_kandev")
|
||||
assert.NotContains(t, tools, "delete_workflow_step_kandev")
|
||||
assert.NotContains(t, tools, "reorder_workflow_steps_kandev")
|
||||
}
|
||||
|
||||
func TestServerModeConfig_RegistersCorrectTools(t *testing.T) {
|
||||
@@ -92,55 +92,55 @@ func TestServerModeConfig_RegistersCorrectTools(t *testing.T) {
|
||||
tools := getRegisteredToolNames(s)
|
||||
|
||||
// Config mode should have workflow config tools
|
||||
assert.Contains(t, tools, "list_workspaces")
|
||||
assert.Contains(t, tools, "list_workflows")
|
||||
assert.Contains(t, tools, "create_workflow")
|
||||
assert.Contains(t, tools, "update_workflow")
|
||||
assert.Contains(t, tools, "delete_workflow")
|
||||
assert.Contains(t, tools, "list_workflow_steps")
|
||||
assert.Contains(t, tools, "create_workflow_step")
|
||||
assert.Contains(t, tools, "update_workflow_step")
|
||||
assert.Contains(t, tools, "delete_workflow_step")
|
||||
assert.Contains(t, tools, "reorder_workflow_steps")
|
||||
assert.Contains(t, tools, "list_workspaces_kandev")
|
||||
assert.Contains(t, tools, "list_workflows_kandev")
|
||||
assert.Contains(t, tools, "create_workflow_kandev")
|
||||
assert.Contains(t, tools, "update_workflow_kandev")
|
||||
assert.Contains(t, tools, "delete_workflow_kandev")
|
||||
assert.Contains(t, tools, "list_workflow_steps_kandev")
|
||||
assert.Contains(t, tools, "create_workflow_step_kandev")
|
||||
assert.Contains(t, tools, "update_workflow_step_kandev")
|
||||
assert.Contains(t, tools, "delete_workflow_step_kandev")
|
||||
assert.Contains(t, tools, "reorder_workflow_steps_kandev")
|
||||
|
||||
// Config mode should have agent tools
|
||||
assert.Contains(t, tools, "list_agents")
|
||||
assert.Contains(t, tools, "update_agent")
|
||||
assert.Contains(t, tools, "create_agent_profile")
|
||||
assert.Contains(t, tools, "delete_agent_profile")
|
||||
assert.Contains(t, tools, "list_agents_kandev")
|
||||
assert.Contains(t, tools, "update_agent_kandev")
|
||||
assert.Contains(t, tools, "create_agent_profile_kandev")
|
||||
assert.Contains(t, tools, "delete_agent_profile_kandev")
|
||||
|
||||
// Config mode should have MCP config tools
|
||||
assert.Contains(t, tools, "list_agent_profiles")
|
||||
assert.Contains(t, tools, "update_agent_profile")
|
||||
assert.Contains(t, tools, "get_mcp_config")
|
||||
assert.Contains(t, tools, "update_mcp_config")
|
||||
assert.Contains(t, tools, "list_agent_profiles_kandev")
|
||||
assert.Contains(t, tools, "update_agent_profile_kandev")
|
||||
assert.Contains(t, tools, "get_mcp_config_kandev")
|
||||
assert.Contains(t, tools, "update_mcp_config_kandev")
|
||||
|
||||
// Config mode should have executor profile tools
|
||||
assert.Contains(t, tools, "list_executors")
|
||||
assert.Contains(t, tools, "list_executor_profiles")
|
||||
assert.Contains(t, tools, "create_executor_profile")
|
||||
assert.Contains(t, tools, "update_executor_profile")
|
||||
assert.Contains(t, tools, "delete_executor_profile")
|
||||
assert.Contains(t, tools, "list_executors_kandev")
|
||||
assert.Contains(t, tools, "list_executor_profiles_kandev")
|
||||
assert.Contains(t, tools, "create_executor_profile_kandev")
|
||||
assert.Contains(t, tools, "update_executor_profile_kandev")
|
||||
assert.Contains(t, tools, "delete_executor_profile_kandev")
|
||||
|
||||
// Config mode should have task tools
|
||||
assert.Contains(t, tools, "list_tasks")
|
||||
assert.Contains(t, tools, "move_task")
|
||||
assert.Contains(t, tools, "delete_task")
|
||||
assert.Contains(t, tools, "archive_task")
|
||||
assert.Contains(t, tools, "update_task_state")
|
||||
assert.Contains(t, tools, "list_tasks_kandev")
|
||||
assert.Contains(t, tools, "move_task_kandev")
|
||||
assert.Contains(t, tools, "delete_task_kandev")
|
||||
assert.Contains(t, tools, "archive_task_kandev")
|
||||
assert.Contains(t, tools, "update_task_state_kandev")
|
||||
|
||||
// Config mode should have interaction tools
|
||||
assert.Contains(t, tools, "ask_user_question")
|
||||
assert.Contains(t, tools, "ask_user_question_kandev")
|
||||
|
||||
// Config mode should NOT have plan tools
|
||||
assert.NotContains(t, tools, "create_task_plan")
|
||||
assert.NotContains(t, tools, "get_task_plan")
|
||||
assert.NotContains(t, tools, "update_task_plan")
|
||||
assert.NotContains(t, tools, "delete_task_plan")
|
||||
assert.NotContains(t, tools, "create_task_plan_kandev")
|
||||
assert.NotContains(t, tools, "get_task_plan_kandev")
|
||||
assert.NotContains(t, tools, "update_task_plan_kandev")
|
||||
assert.NotContains(t, tools, "delete_task_plan_kandev")
|
||||
|
||||
// Config mode should NOT have task-mode kanban create/update tools
|
||||
assert.NotContains(t, tools, "create_task")
|
||||
assert.NotContains(t, tools, "update_task")
|
||||
assert.NotContains(t, tools, "create_task_kandev")
|
||||
assert.NotContains(t, tools, "update_task_kandev")
|
||||
}
|
||||
|
||||
func TestServerModeDefault_DefaultsToTask(t *testing.T) {
|
||||
@@ -153,9 +153,9 @@ func TestServerModeDefault_DefaultsToTask(t *testing.T) {
|
||||
assert.Equal(t, ModeTask, s.mode)
|
||||
|
||||
tools := getRegisteredToolNames(s)
|
||||
assert.Contains(t, tools, "create_task")
|
||||
assert.Contains(t, tools, "create_task_plan")
|
||||
assert.NotContains(t, tools, "create_workflow_step")
|
||||
assert.Contains(t, tools, "create_task_kandev")
|
||||
assert.Contains(t, tools, "create_task_plan_kandev")
|
||||
assert.NotContains(t, tools, "create_workflow_step_kandev")
|
||||
}
|
||||
|
||||
func TestServerModeConfig_DisableAskQuestion(t *testing.T) {
|
||||
@@ -167,9 +167,9 @@ func TestServerModeConfig_DisableAskQuestion(t *testing.T) {
|
||||
require.NotNil(t, s)
|
||||
|
||||
tools := getRegisteredToolNames(s)
|
||||
assert.NotContains(t, tools, "ask_user_question")
|
||||
assert.Contains(t, tools, "list_agents")
|
||||
assert.Contains(t, tools, "create_workflow_step")
|
||||
assert.NotContains(t, tools, "ask_user_question_kandev")
|
||||
assert.Contains(t, tools, "list_agents_kandev")
|
||||
assert.Contains(t, tools, "create_workflow_step_kandev")
|
||||
}
|
||||
|
||||
func TestServerModeTask_DisableAskQuestion(t *testing.T) {
|
||||
@@ -181,9 +181,9 @@ func TestServerModeTask_DisableAskQuestion(t *testing.T) {
|
||||
require.NotNil(t, s)
|
||||
|
||||
tools := getRegisteredToolNames(s)
|
||||
assert.NotContains(t, tools, "ask_user_question")
|
||||
assert.Contains(t, tools, "create_task")
|
||||
assert.Contains(t, tools, "create_task_plan")
|
||||
assert.NotContains(t, tools, "ask_user_question_kandev")
|
||||
assert.Contains(t, tools, "create_task_kandev")
|
||||
assert.Contains(t, tools, "create_task_plan_kandev")
|
||||
}
|
||||
|
||||
func TestServerModeTask_ToolCount(t *testing.T) {
|
||||
@@ -217,9 +217,9 @@ func TestServerModeConfig_ToolDescriptions(t *testing.T) {
|
||||
|
||||
toolsMap := s.mcpServer.ListTools()
|
||||
|
||||
assert.Contains(t, toolsMap["create_workflow_step"].Tool.Description, "Create a new workflow step")
|
||||
assert.Contains(t, toolsMap["list_agents"].Tool.Description, "List all configured agents")
|
||||
assert.Contains(t, toolsMap["get_mcp_config"].Tool.Description, "Get MCP server configuration")
|
||||
assert.Contains(t, toolsMap["create_workflow_step_kandev"].Tool.Description, "Create a new workflow step")
|
||||
assert.Contains(t, toolsMap["list_agents_kandev"].Tool.Description, "List all configured agents")
|
||||
assert.Contains(t, toolsMap["get_mcp_config_kandev"].Tool.Description, "Get MCP server configuration")
|
||||
}
|
||||
|
||||
func TestServerModeConstants(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kandev/kandev/internal/sysprompt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// kandevToolRef matches any `<name>_kandev` identifier appearing in a prompt.
|
||||
// The regex enforces snake_case and requires the _kandev suffix at a word boundary.
|
||||
var kandevToolRef = regexp.MustCompile(`\b[a-z][a-z0-9_]*_kandev\b`)
|
||||
|
||||
// extractKandevTools returns the unique set of "<name>_kandev" tool names
|
||||
// referenced anywhere inside the given prompt text.
|
||||
func extractKandevTools(prompt string) map[string]struct{} {
|
||||
out := make(map[string]struct{})
|
||||
for _, m := range kandevToolRef.FindAllString(prompt, -1) {
|
||||
out[m] = struct{}{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// findBareToolReferences scans `prompt` for any whole-word occurrence of a
|
||||
// `bareName`. Returns the sorted list of bare names found. Used to catch
|
||||
// sysprompt drift in the opposite direction of the v0.28 bug — i.e. a prompt
|
||||
// that says `get_task_plan` (no suffix) when the registered tool is
|
||||
// `get_task_plan_kandev`.
|
||||
//
|
||||
// `\b` in Go's RE2 fires at a transition between a word char (`[A-Za-z0-9_]`)
|
||||
// and a non-word char. Since `_` is a word char, `\b<bare>\b` cannot match
|
||||
// inside `<bare>_kandev` — the trailing `\b` requires a non-word char after the
|
||||
// last letter of `bare`, and `_` fails that test. So this regex naturally
|
||||
// distinguishes bare references from the suffixed form without an explicit
|
||||
// suffix guard.
|
||||
func findBareToolReferences(prompt string, bareNames map[string]struct{}) []string {
|
||||
var found []string
|
||||
for bare := range bareNames {
|
||||
re := regexp.MustCompile(`\b` + regexp.QuoteMeta(bare) + `\b`)
|
||||
if re.MatchString(prompt) {
|
||||
found = append(found, bare)
|
||||
}
|
||||
}
|
||||
sort.Strings(found)
|
||||
return found
|
||||
}
|
||||
|
||||
// bareNamesOf strips the `_kandev` suffix from every registered tool name and
|
||||
// returns the bare set, used by findBareToolReferences as the haystack of
|
||||
// candidates to scan for.
|
||||
func bareNamesOf(registered map[string]struct{}) map[string]struct{} {
|
||||
const suffix = "_kandev"
|
||||
out := make(map[string]struct{}, len(registered))
|
||||
for name := range registered {
|
||||
out[strings.TrimSuffix(name, suffix)] = struct{}{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// TestSyspromptToolNames_MatchMCPTaskMode verifies that every `<name>_kandev`
|
||||
// tool referenced in the task-mode prompts (PlanMode, KandevContext,
|
||||
// DefaultPlanPrefix) is actually registered by an MCP server in ModeTask.
|
||||
//
|
||||
// This is the regression test for the v0.28 bug where the sysprompt told
|
||||
// agents to call tools like `get_task_plan_kandev` but the MCP server
|
||||
// registered them as `get_task_plan` (no suffix), causing "Tool not found"
|
||||
// errors at runtime.
|
||||
func TestSyspromptToolNames_MatchMCPTaskMode(t *testing.T) {
|
||||
log := newTestLogger(t)
|
||||
backend := NewChannelBackendClient(log)
|
||||
defer backend.Close()
|
||||
|
||||
s := New(backend, "test-session", "test-task", 10005, log, "", false, ModeTask)
|
||||
require.NotNil(t, s)
|
||||
|
||||
registered := make(map[string]struct{})
|
||||
for _, name := range getRegisteredToolNames(s) {
|
||||
registered[name] = struct{}{}
|
||||
}
|
||||
|
||||
referenced := make(map[string]struct{})
|
||||
for name := range extractKandevTools(sysprompt.PlanMode) {
|
||||
referenced[name] = struct{}{}
|
||||
}
|
||||
for name := range extractKandevTools(sysprompt.KandevContext) {
|
||||
referenced[name] = struct{}{}
|
||||
}
|
||||
for name := range extractKandevTools(sysprompt.DefaultPlanPrefix) {
|
||||
referenced[name] = struct{}{}
|
||||
}
|
||||
|
||||
require.NotEmpty(t, referenced, "expected task-mode prompts to reference at least one _kandev tool")
|
||||
|
||||
for name := range referenced {
|
||||
_, ok := registered[name]
|
||||
assert.True(t, ok,
|
||||
"tool %q is referenced in task-mode sysprompt but not registered by the MCP server in ModeTask",
|
||||
name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSyspromptToolNames_MatchMCPConfigMode verifies that every `<name>_kandev`
|
||||
// tool referenced in ConfigContext is registered by an MCP server in ModeConfig.
|
||||
func TestSyspromptToolNames_MatchMCPConfigMode(t *testing.T) {
|
||||
log := newTestLogger(t)
|
||||
backend := NewChannelBackendClient(log)
|
||||
defer backend.Close()
|
||||
|
||||
s := New(backend, "test-session", "test-task", 10005, log, "", false, ModeConfig)
|
||||
require.NotNil(t, s)
|
||||
|
||||
registered := make(map[string]struct{})
|
||||
for _, name := range getRegisteredToolNames(s) {
|
||||
registered[name] = struct{}{}
|
||||
}
|
||||
|
||||
referenced := extractKandevTools(sysprompt.ConfigContext)
|
||||
require.NotEmpty(t, referenced, "expected ConfigContext to reference at least one _kandev tool")
|
||||
|
||||
for name := range referenced {
|
||||
_, ok := registered[name]
|
||||
assert.True(t, ok,
|
||||
"tool %q is referenced in ConfigContext but not registered by the MCP server in ModeConfig",
|
||||
name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSyspromptToolNames_NoBareToolReferences catches the opposite drift: a
|
||||
// prompt that mentions a registered tool by its bare name (without the
|
||||
// `_kandev` suffix). Without this check, a typo like
|
||||
// `get_task_plan` in a sysprompt would silently slip past the other tests
|
||||
// because they only inspect `_kandev`-suffixed mentions.
|
||||
func TestSyspromptToolNames_NoBareToolReferences(t *testing.T) {
|
||||
log := newTestLogger(t)
|
||||
backend := NewChannelBackendClient(log)
|
||||
defer backend.Close()
|
||||
|
||||
taskServer := New(backend, "test-session", "test-task", 10005, log, "", false, ModeTask)
|
||||
require.NotNil(t, taskServer)
|
||||
configServer := New(backend, "test-session", "test-task", 10005, log, "", false, ModeConfig)
|
||||
require.NotNil(t, configServer)
|
||||
|
||||
registered := make(map[string]struct{})
|
||||
for _, name := range getRegisteredToolNames(taskServer) {
|
||||
registered[name] = struct{}{}
|
||||
}
|
||||
for _, name := range getRegisteredToolNames(configServer) {
|
||||
registered[name] = struct{}{}
|
||||
}
|
||||
bareNames := bareNamesOf(registered)
|
||||
|
||||
cases := map[string]string{
|
||||
"PlanMode": sysprompt.PlanMode,
|
||||
"KandevContext": sysprompt.KandevContext,
|
||||
"DefaultPlanPrefix": sysprompt.DefaultPlanPrefix,
|
||||
"ConfigContext": sysprompt.ConfigContext,
|
||||
}
|
||||
|
||||
for name, prompt := range cases {
|
||||
bare := findBareToolReferences(prompt, bareNames)
|
||||
assert.Empty(t, bare,
|
||||
"sysprompt %s contains tool name(s) without the _kandev suffix: %v — every reference must use the suffixed form",
|
||||
name, bare)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFindBareToolReferences_DistinguishesSuffixedFromBare locks in the
|
||||
// regex-word-boundary contract that findBareToolReferences relies on. If a
|
||||
// future Go release changes `\b` semantics for `_`, the production tests above
|
||||
// would silently start passing or failing for the wrong reasons; this unit
|
||||
// test surfaces the change at its source.
|
||||
func TestFindBareToolReferences_DistinguishesSuffixedFromBare(t *testing.T) {
|
||||
bareNames := map[string]struct{}{
|
||||
"create_task_plan": {},
|
||||
"list_executors": {},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
prompt string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "bare name in prose is flagged",
|
||||
prompt: "Use list_executors to find IDs.",
|
||||
want: []string{"list_executors"},
|
||||
},
|
||||
{
|
||||
name: "suffixed name is not flagged",
|
||||
prompt: "Use list_executors_kandev to find IDs.",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "both forms in same prompt: only bare is flagged",
|
||||
prompt: "list_executors_kandev (bare: list_executors) returns IDs.",
|
||||
want: []string{"list_executors"},
|
||||
},
|
||||
{
|
||||
name: "multiple bare names sorted in output",
|
||||
prompt: "Call create_task_plan then list_executors.",
|
||||
want: []string{"create_task_plan", "list_executors"},
|
||||
},
|
||||
{
|
||||
name: "no references",
|
||||
prompt: "Hello world.",
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := findBareToolReferences(tc.prompt, bareNames)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ AGENT TOOLS:
|
||||
- delete_agent_profile_kandev: Delete an agent profile. Required: profile_id.
|
||||
|
||||
EXECUTOR PROFILE TOOLS:
|
||||
Executors (local, worktree, local_docker, sprites) are pre-defined. Use list_executors to find executor IDs, then manage profiles.
|
||||
Executors (local, worktree, local_docker, sprites) are pre-defined. Use list_executors_kandev to find executor IDs, then manage profiles.
|
||||
- list_executors_kandev: List all executors with their IDs and types.
|
||||
- list_executor_profiles_kandev: List profiles for an executor. Required: executor_id.
|
||||
- create_executor_profile_kandev: Create an executor profile. Required: executor_id, name. Optional: mcp_policy, config, prepare_script, cleanup_script.
|
||||
|
||||
@@ -45,15 +45,15 @@ test.describe("Config-mode MCP — workflow management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Listing workspaces...")',
|
||||
"e2e:mcp:kandev:list_workspaces({})",
|
||||
`e2e:mcp:kandev:list_workflows({"workspace_id":"${seedData.workspaceId}"})`,
|
||||
"e2e:mcp:kandev:list_workspaces_kandev({})",
|
||||
`e2e:mcp:kandev:list_workflows_kandev({"workspace_id":"${seedData.workspaceId}"})`,
|
||||
'e2e:message("Done listing")',
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const page = await runAndWait(testPage, session.task_id, "Done listing");
|
||||
await expect(page.chat.getByText("list_workspaces")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("list_workflows")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("list_workspaces_kandev")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("list_workflows_kandev")).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("agent can create and list workflow steps", async ({ testPage, apiClient, seedData }) => {
|
||||
@@ -64,8 +64,8 @@ test.describe("Config-mode MCP — workflow management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Creating step...")',
|
||||
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"QA Review","position":0})`,
|
||||
`e2e:mcp:kandev:list_workflow_steps({"workflow_id":"${workflow.id}"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"QA Review","position":0})`,
|
||||
`e2e:mcp:kandev:list_workflow_steps_kandev({"workflow_id":"${workflow.id}"})`,
|
||||
'e2e:message("Steps listed")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -104,7 +104,7 @@ test.describe("Config-mode MCP — workflow management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Creating step with all fields...")',
|
||||
`e2e:mcp:kandev:create_workflow_step(${createArgs})`,
|
||||
`e2e:mcp:kandev:create_workflow_step_kandev(${createArgs})`,
|
||||
'e2e:message("Step created")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -129,7 +129,7 @@ test.describe("Config-mode MCP — workflow management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Creating workflow...")',
|
||||
`e2e:mcp:kandev:create_workflow({"workspace_id":"${seedData.workspaceId}","name":"E2E Workflow","description":"Created by E2E test"})`,
|
||||
`e2e:mcp:kandev:create_workflow_kandev({"workspace_id":"${seedData.workspaceId}","name":"E2E Workflow","description":"Created by E2E test"})`,
|
||||
'e2e:message("Workflow created")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -153,7 +153,7 @@ test.describe("Config-mode MCP — workflow management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Updating workflow...")',
|
||||
`e2e:mcp:kandev:update_workflow({"workflow_id":"${workflow.id}","name":"After Update","description":"Updated description"})`,
|
||||
`e2e:mcp:kandev:update_workflow_kandev({"workflow_id":"${workflow.id}","name":"After Update","description":"Updated description"})`,
|
||||
'e2e:message("Workflow updated")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -179,7 +179,7 @@ test.describe("Config-mode MCP — workflow management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Deleting workflow...")',
|
||||
`e2e:mcp:kandev:delete_workflow({"workflow_id":"${workflow.id}"})`,
|
||||
`e2e:mcp:kandev:delete_workflow_kandev({"workflow_id":"${workflow.id}"})`,
|
||||
'e2e:message("Workflow deleted")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -213,7 +213,7 @@ test.describe("Config-mode MCP — workflow management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Updating step...")',
|
||||
`e2e:mcp:kandev:update_workflow_step(${updateArgs})`,
|
||||
`e2e:mcp:kandev:update_workflow_step_kandev(${updateArgs})`,
|
||||
'e2e:message("Step updated")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -247,15 +247,17 @@ test.describe("Config-mode MCP — agent management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Listing agents...")',
|
||||
"e2e:mcp:kandev:list_agents({})",
|
||||
`e2e:mcp:kandev:list_agent_profiles({"agent_id":"${agent.id}"})`,
|
||||
"e2e:mcp:kandev:list_agents_kandev({})",
|
||||
`e2e:mcp:kandev:list_agent_profiles_kandev({"agent_id":"${agent.id}"})`,
|
||||
'e2e:message("Agents listed")',
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const page = await runAndWait(testPage, session.task_id, "Agents listed");
|
||||
await expect(page.chat.getByText("list_agents")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("list_agent_profiles")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("list_agents_kandev")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("list_agent_profiles_kandev")).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
});
|
||||
|
||||
test("agent can create and delete an agent profile", async ({
|
||||
@@ -273,7 +275,7 @@ test.describe("Config-mode MCP — agent management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Creating profile...")',
|
||||
`e2e:mcp:kandev:create_agent_profile({"agent_id":"${agent.id}","name":"E2E Created Profile","model":"claude-sonnet-4-5-20250514"})`,
|
||||
`e2e:mcp:kandev:create_agent_profile_kandev({"agent_id":"${agent.id}","name":"E2E Created Profile","model":"claude-sonnet-4-5-20250514"})`,
|
||||
'e2e:message("Profile created")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -296,7 +298,7 @@ test.describe("Config-mode MCP — agent management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Deleting profile...")',
|
||||
`e2e:mcp:kandev:delete_agent_profile({"profile_id":"${newProfileId}"})`,
|
||||
`e2e:mcp:kandev:delete_agent_profile_kandev({"profile_id":"${newProfileId}"})`,
|
||||
'e2e:message("Profile deleted")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -319,7 +321,7 @@ test.describe("Config-mode MCP — agent management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Updating agent...")',
|
||||
`e2e:mcp:kandev:update_agent({"agent_id":"${agent.id}","supports_mcp":true})`,
|
||||
`e2e:mcp:kandev:update_agent_kandev({"agent_id":"${agent.id}","supports_mcp":true})`,
|
||||
'e2e:message("Agent updated")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -338,7 +340,7 @@ test.describe("Config-mode MCP — agent management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Updating profile...")',
|
||||
`e2e:mcp:kandev:update_agent_profile({"profile_id":"${seedData.agentProfileId}","name":"Renamed Profile"})`,
|
||||
`e2e:mcp:kandev:update_agent_profile_kandev({"profile_id":"${seedData.agentProfileId}","name":"Renamed Profile"})`,
|
||||
'e2e:message("Profile updated")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -363,7 +365,7 @@ test.describe("Config-mode MCP — agent management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Updating profile settings...")',
|
||||
`e2e:mcp:kandev:update_agent_profile({"profile_id":"${seedData.agentProfileId}","model":"claude-sonnet-4-5-20250514","auto_approve":false})`,
|
||||
`e2e:mcp:kandev:update_agent_profile_kandev({"profile_id":"${seedData.agentProfileId}","model":"claude-sonnet-4-5-20250514","auto_approve":false})`,
|
||||
'e2e:message("Profile settings updated")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -391,15 +393,15 @@ test.describe("Config-mode MCP — MCP server configuration", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Reading MCP config...")',
|
||||
`e2e:mcp:kandev:get_mcp_config({"profile_id":"${seedData.agentProfileId}"})`,
|
||||
`e2e:mcp:kandev:update_mcp_config({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"test-server":{"command":"node","args":["server.js"]}}})`,
|
||||
`e2e:mcp:kandev:get_mcp_config_kandev({"profile_id":"${seedData.agentProfileId}"})`,
|
||||
`e2e:mcp:kandev:update_mcp_config_kandev({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"test-server":{"command":"node","args":["server.js"]}}})`,
|
||||
'e2e:message("MCP config updated")',
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const page = await runAndWait(testPage, session.task_id, "MCP config updated");
|
||||
await expect(page.chat.getByText("get_mcp_config")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("update_mcp_config")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("get_mcp_config_kandev")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("update_mcp_config_kandev")).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// Verify via API
|
||||
const config = await apiClient.getAgentProfileMcpConfig(seedData.agentProfileId);
|
||||
@@ -425,13 +427,13 @@ test.describe("Config-mode MCP — task management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Listing tasks...")',
|
||||
`e2e:mcp:kandev:list_tasks({"workflow_id":"${seedData.workflowId}"})`,
|
||||
`e2e:mcp:kandev:list_tasks_kandev({"workflow_id":"${seedData.workflowId}"})`,
|
||||
'e2e:message("Tasks listed")',
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const page = await runAndWait(testPage, session.task_id, "Tasks listed");
|
||||
await expect(page.chat.getByText("list_tasks").first()).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.chat.getByText("list_tasks_kandev").first()).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("agent can move a task to a different step", async ({ testPage, apiClient, seedData }) => {
|
||||
@@ -450,7 +452,7 @@ test.describe("Config-mode MCP — task management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Moving task...")',
|
||||
`e2e:mcp:kandev:move_task({"task_id":"${task.id}","workflow_id":"${seedData.workflowId}","workflow_step_id":"${targetStep!.id}"})`,
|
||||
`e2e:mcp:kandev:move_task_kandev({"task_id":"${task.id}","workflow_id":"${seedData.workflowId}","workflow_step_id":"${targetStep!.id}"})`,
|
||||
'e2e:message("Task moved")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -474,7 +476,7 @@ test.describe("Config-mode MCP — task management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Archiving task...")',
|
||||
`e2e:mcp:kandev:archive_task({"task_id":"${task.id}"})`,
|
||||
`e2e:mcp:kandev:archive_task_kandev({"task_id":"${task.id}"})`,
|
||||
'e2e:message("Task archived")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -497,7 +499,7 @@ test.describe("Config-mode MCP — task management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Deleting task...")',
|
||||
`e2e:mcp:kandev:delete_task({"task_id":"${task.id}"})`,
|
||||
`e2e:mcp:kandev:delete_task_kandev({"task_id":"${task.id}"})`,
|
||||
'e2e:message("Task deleted")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -521,13 +523,13 @@ test.describe("Config-mode MCP — executor management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Listing executors...")',
|
||||
"e2e:mcp:kandev:list_executors({})",
|
||||
"e2e:mcp:kandev:list_executors_kandev({})",
|
||||
'e2e:message("Executors listed")',
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const page = await runAndWait(testPage, session.task_id, "Executors listed");
|
||||
await expect(page.chat.getByText("list_executors", { exact: true })).toBeVisible({
|
||||
await expect(page.chat.getByText("list_executors_kandev", { exact: true })).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
});
|
||||
@@ -547,7 +549,7 @@ test.describe("Config-mode MCP — executor management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Creating executor profile...")',
|
||||
`e2e:mcp:kandev:create_executor_profile({"executor_id":"${executor.id}","name":"E2E Profile"})`,
|
||||
`e2e:mcp:kandev:create_executor_profile_kandev({"executor_id":"${executor.id}","name":"E2E Profile"})`,
|
||||
'e2e:message("Executor profile created")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -566,7 +568,7 @@ test.describe("Config-mode MCP — executor management", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Deleting executor profile...")',
|
||||
`e2e:mcp:kandev:delete_executor_profile({"profile_id":"${profile!.id}"})`,
|
||||
`e2e:mcp:kandev:delete_executor_profile_kandev({"profile_id":"${profile!.id}"})`,
|
||||
'e2e:message("Executor profile deleted")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -597,10 +599,10 @@ test.describe("Config-mode MCP — multi-tool workflow", () => {
|
||||
seedData,
|
||||
[
|
||||
'e2e:message("Starting multi-tool config...")',
|
||||
"e2e:mcp:kandev:list_workspaces({})",
|
||||
`e2e:mcp:kandev:list_workflows({"workspace_id":"${seedData.workspaceId}"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Agent Created Step","position":0})`,
|
||||
"e2e:mcp:kandev:list_agents({})",
|
||||
"e2e:mcp:kandev:list_workspaces_kandev({})",
|
||||
`e2e:mcp:kandev:list_workflows_kandev({"workspace_id":"${seedData.workspaceId}"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Agent Created Step","position":0})`,
|
||||
"e2e:mcp:kandev:list_agents_kandev({})",
|
||||
'e2e:message("Multi-tool config complete")',
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -628,13 +630,13 @@ test.describe("Config-mode MCP — multi-tool workflow", () => {
|
||||
[
|
||||
'e2e:message("Setting up full workflow...")',
|
||||
// Create 3 steps
|
||||
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Build","position":0,"color":"#3b82f6"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Test","position":1,"color":"#eab308"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step({"workflow_id":"${workflow.id}","name":"Deploy","position":2,"color":"#22c55e"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Build","position":0,"color":"#3b82f6"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Test","position":1,"color":"#eab308"})`,
|
||||
`e2e:mcp:kandev:create_workflow_step_kandev({"workflow_id":"${workflow.id}","name":"Deploy","position":2,"color":"#22c55e"})`,
|
||||
// Create a new agent profile
|
||||
`e2e:mcp:kandev:create_agent_profile({"agent_id":"${agent.id}","name":"CI Profile","model":"claude-sonnet-4-5-20250514"})`,
|
||||
`e2e:mcp:kandev:create_agent_profile_kandev({"agent_id":"${agent.id}","name":"CI Profile","model":"claude-sonnet-4-5-20250514"})`,
|
||||
// Update MCP config on the test profile
|
||||
`e2e:mcp:kandev:update_mcp_config({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"ci-tools":{"command":"npx","args":["-y","@ci/tools"]}}})`,
|
||||
`e2e:mcp:kandev:update_mcp_config_kandev({"profile_id":"${seedData.agentProfileId}","enabled":true,"servers":{"ci-tools":{"command":"npx","args":["-y","@ci/tools"]}}})`,
|
||||
'e2e:message("Full setup complete")',
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
@@ -173,7 +173,7 @@ test.describe("Task creation", () => {
|
||||
const script = [
|
||||
'e2e:thinking("Analyzing task and creating plan...")',
|
||||
"e2e:delay(100)",
|
||||
'e2e:mcp:kandev:create_task_plan({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
|
||||
'e2e:mcp:kandev:create_task_plan_kandev({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
|
||||
"e2e:delay(100)",
|
||||
'e2e:message("I\'ve created an implementation plan for this task.")',
|
||||
].join("\n");
|
||||
|
||||
@@ -12,7 +12,7 @@ import { SessionPage } from "../../pages/session-page";
|
||||
const PLAN_SCRIPT = [
|
||||
'e2e:thinking("Creating plan...")',
|
||||
"e2e:delay(100)",
|
||||
'e2e:mcp:kandev:create_task_plan({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
|
||||
'e2e:mcp:kandev:create_task_plan_kandev({"task_id":"{task_id}","content":"## Plan\\n\\n1. Analyze requirements\\n2. Implement solution\\n3. Write tests","title":"Implementation Plan"})',
|
||||
"e2e:delay(100)",
|
||||
'e2e:message("Plan created.")',
|
||||
].join("\n");
|
||||
|
||||
@@ -107,7 +107,7 @@ test.describe("MCP subtask creation", () => {
|
||||
const script = [
|
||||
'e2e:thinking("Planning subtasks...")',
|
||||
"e2e:delay(100)",
|
||||
`e2e:mcp:kandev:create_task({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify MCP create_task with parent_id"})`,
|
||||
`e2e:mcp:kandev:create_task_kandev({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify MCP create_task with parent_id"})`,
|
||||
"e2e:delay(100)",
|
||||
'e2e:message("Done.")',
|
||||
].join("\n");
|
||||
@@ -159,7 +159,7 @@ test.describe("MCP subtask creation", () => {
|
||||
const script = [
|
||||
'e2e:thinking("Creating subtask...")',
|
||||
"e2e:delay(100)",
|
||||
`e2e:mcp:kandev:create_task({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify repository inheritance"})`,
|
||||
`e2e:mcp:kandev:create_task_kandev({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify repository inheritance"})`,
|
||||
"e2e:delay(100)",
|
||||
'e2e:message("Done.")',
|
||||
].join("\n");
|
||||
@@ -275,7 +275,7 @@ test.describe("Subtask inheritance", () => {
|
||||
const script = [
|
||||
'e2e:thinking("Creating subtask...")',
|
||||
"e2e:delay(100)",
|
||||
`e2e:mcp:kandev:create_task({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify executor and agent profile inheritance"})`,
|
||||
`e2e:mcp:kandev:create_task_kandev({"parent_id":"self","title":"${subtaskTitle}","description":"E2E subtask: verify executor and agent profile inheritance"})`,
|
||||
"e2e:delay(100)",
|
||||
'e2e:message("Done.")',
|
||||
].join("\n");
|
||||
|
||||
Reference in New Issue
Block a user