Skip to content

Commit 2903467

Browse files
committed
Fixes and improvements
1 parent b864f43 commit 2903467

3 files changed

Lines changed: 45 additions & 20 deletions

File tree

formatters/ai_scriptless.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,31 @@
1111
TestStructure,
1212
)
1313

14+
PRIMARY_AI_COMMAND_IDS = (
15+
"ai_user-action",
16+
"ai_validation",
17+
"ai_visual-comparison",
18+
)
19+
20+
21+
def command_selection_policy_info() -> List[str]:
22+
"""Context returned with list_commands so agents load policy when choosing command_ids."""
23+
return [
24+
"Command selection policy (when authoring tests with add_command / modify_command):",
25+
"Default: use only these primary AI command_ids: "
26+
+ ", ".join(PRIMARY_AI_COMMAND_IDS) + ".",
27+
" • ai_user-action — user interactions (open browser/app, navigate to URL, tap, type, dismiss overlays); "
28+
"argument: action (natural language).",
29+
" • ai_validation — checkpoints and assertions; argument: validation (natural language).",
30+
" • ai_visual-comparison — visual/baseline comparison; argument: name.",
31+
"Prefer ai_user-action for navigation (e.g. open browser and go to URL), not browser_goto / browser_open.",
32+
"Do not use browser_*, touch_tap, webpage.element_*, checkpoint_text, etc. unless the user explicitly "
33+
"requests a non-AI command or agreed that AI commands cannot meet a documented requirement.",
34+
"Structural helpers (add_logical_step, add_loop, add_condition, comment, wait) are OK; "
35+
"keep observable steps AI-driven when possible.",
36+
"Call get_command_definitions only for the AI command_ids you will use.",
37+
]
38+
1439
def format_ai_scriptless_tests_filter_values(tests: dict[str, Any], params: Optional[dict] = None) -> dict[str, Any]:
1540
filter_values = {
1641
"test_name": [],

tools/ai_scriptless_manager.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
from config.perfecto import TOOLS_PREFIX, SUPPORT_MESSAGE
1212
from config.token import PerfectoToken, token_verify
1313
from formatters.ai_scriptless import format_ai_scriptless_tests, \
14-
format_ai_scriptless_tests_filter_values, format_command_catalog, \
15-
format_command_definitions, format_snapshots_list, format_test_structure, \
16-
format_test_variables
14+
format_ai_scriptless_tests_filter_values, command_selection_policy_info, \
15+
format_command_catalog, format_command_definitions, format_snapshots_list, \
16+
format_test_structure, format_test_variables
1717
from models.manager import Manager
1818
from models.result import BaseResult, PaginationResult
1919
from tools.ai_scriptless_script import (
@@ -52,7 +52,6 @@
5252
"do not reuse step_path values from this response.",
5353
]
5454

55-
5655
def _append_step_path_refresh_notes(result: BaseResult) -> BaseResult:
5756
if result.error or not isinstance(result.result, dict):
5857
return result
@@ -196,8 +195,11 @@ async def list_commands(self, checkpoint: bool = False) -> BaseResult:
196195
commands_url = commands_url + "/commands"
197196
if checkpoint:
198197
commands_url = commands_url + "?checkpoint=true"
199-
return await api_request(self.token, "GET", endpoint=commands_url,
200-
result_formatter=format_command_catalog)
198+
result = await api_request(self.token, "GET", endpoint=commands_url,
199+
result_formatter=format_command_catalog)
200+
if not result.error:
201+
result.append_info(command_selection_policy_info())
202+
return result
201203

202204
@token_verify
203205
async def get_command_definitions(self, command_ids: list[str]) -> BaseResult:
@@ -484,9 +486,8 @@ async def move_test(
484486
return BaseResult(error=str(exc))
485487

486488
target_visibility = visibility or source_visibility
487-
owner = await fetch_current_username(self.token) or ""
488-
move_url = perfecto.get_repository_management_api_url(self.token.cloud_name) + "/directory"
489-
body = build_move_test_body(test_id, folder, target_visibility, owner)
489+
move_url = perfecto.get_repository_management_api_url(self.token.cloud_name) + "/artifacts"
490+
body = build_move_test_body(test_id, folder, target_visibility)
490491
result = await api_request(self.token, "PATCH", endpoint=move_url, json=body)
491492
if result.error:
492493
return result
@@ -647,11 +648,12 @@ def register(mcp, token: Optional[PerfectoToken]):
647648
args(dict): Dictionary with the following required parameters:
648649
test_id (str): Test itemKey from list_tests (e.g. PRIVATE:My Folder/My Test.xml).
649650
- list_commands: List available AI Scriptless commands from the command repository.
651+
Returns the catalog in result and command selection policy in info (read info before add_command when authoring tests).
650652
args(dict): Dictionary with the following optional parameters:
651653
checkpoint (bool, default=false): If true, list checkpoint commands only.
652654
- get_command_definitions: Get parameter definitions for one or more commands.
653655
args(dict): Dictionary with the following required parameters:
654-
command_ids (list[str]): Command IDs from list_commands (e.g. ai_user-action, checkpoint_text).
656+
command_ids (list[str]): Command IDs from list_commands (typically ai_user-action, ai_validation, ai_visual-comparison).
655657
- add_command: Add a command to a test and persist it.
656658
args(dict): Dictionary with the following parameters:
657659
test_id (str, required): Test itemKey from list_tests.
@@ -725,8 +727,9 @@ def register(mcp, token: Optional[PerfectoToken]):
725727
- move_test: Move a test to another folder (same or different visibility).
726728
args(dict): Dictionary with the following required parameters:
727729
test_id (str): Source test itemKey from list_tests.
728-
folder (str): Target folder path without visibility prefix (e.g. 'My Folder' or 'Shared/Team').
730+
folder (str): Target folder path without visibility prefix (e.g. 'My Folder', 'MCP Archive', or 'My Folder/SubFolder'). The test file keeps its name. If the path does not exist, the API creates the nested folder segments automatically (the new folder may not appear as a CONTAINER in list_tests until it contains tests).
729731
visibility (str, optional): Target visibility; defaults to the source test visibility.
732+
Returns source_item_key and target_item_key; use target_item_key for view_test_structure, execute_test, and other actions after the move.
730733
- list_snapshots: List snapshot history for a test (includes '<current>' marker plus UUID historical versions).
731734
args(dict): Dictionary with the following required parameters:
732735
test_id (str): Test itemKey from list_tests.
@@ -756,8 +759,8 @@ def register(mcp, token: Optional[PerfectoToken]):
756759
test_id (str): Test itemKey from list_tests.
757760
name (str): Variable name to delete.
758761
Hints:
762+
- When authoring or editing test steps, call list_commands first and follow the command selection policy in the info field.
759763
- step_path is a dot-separated positional path without spaces (0-based indices; b0=Then branch, b1=Else). Example: root step 3 is "3"; first step inside Then of condition at 5 is "5.b0.0". Perfecto does not persist paths; they change when steps are inserted, moved, or deleted. Always call view_test_structure before the next structure edit; do not reuse step_path from a previous mutation response.
760-
- Use list_commands and get_command_definitions before add_command to discover valid command_ids and argument names.
761764
- Use parent_path on add_command with the step_path of a LogicalStep, Loop, or Branch from view_test_structure.
762765
- Use add_logical_step, add_loop, and add_condition to build control-flow structures matching the UI toolbar Group, Loop, and Condition actions.
763766
- Script variables (list_test_variables, add/modify/delete_test_variable) are stored in script.variables[] and are distinct from the DUT parameter in script.parameters[].

tools/ai_scriptless_script.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,18 @@ def build_move_test_body(
4949
test_id: str,
5050
folder: str,
5151
visibility: str,
52-
owner: str,
5352
) -> dict[str, Any]:
5453
src_visibility, src_path = split_item_key(test_id)
5554
file_name = test_file_name(test_id)
5655
target_folder = folder.strip("/")
57-
target_directory = f"{target_folder}/{file_name}" if target_folder else file_name
56+
target_artifact_id = f"{target_folder}/{file_name}" if target_folder else file_name
5857
return {
59-
"copy": False,
60-
"directoryPath": src_path,
58+
"repositoryType": "SCRIPTS",
59+
"keyDetails": {"artifactId": src_path, "version": "v0"},
6160
"folderType": folder_type(src_visibility),
62-
"targetDirectoryPath": target_directory,
61+
"targetKeyDetails": {"artifactId": target_artifact_id, "version": "v0"},
6362
"targetFolderType": folder_type(visibility),
64-
"repositoryType": "SCRIPTS",
65-
"owner": owner,
66-
"groupKey": None,
63+
"copy": False,
6764
}
6865

6966

0 commit comments

Comments
 (0)