feat(agent): redesign project_cli with module/method/input protocol
- Replace command/subcommand/args with module/method/input envelope - Calendar handler uses discriminated union (mode) for read operations - Strict Pydantic models with extra='forbid' for all calendar methods - Worker max_iters=7, router prompt simplified (removed project_cli_defaults) - Skill index cards + per-action files for progressive disclosure - Frontend/AG-UI aligned to module/method dispatch - Protocol docs updated to module/method/input contract WIP: action cards need envelope fix, 2 tests need update, memory handler needs Pydantic models.
This commit is contained in:
@@ -32,7 +32,9 @@ def test_react_agent_sys_prompt_includes_registered_skill_prompt() -> None:
|
||||
assert "# Agent Skills" in prompt
|
||||
assert "## calendar" in prompt
|
||||
assert "## contacts" in prompt
|
||||
assert "SKILL.md" in prompt
|
||||
assert "view_skill_file" in prompt
|
||||
assert 'file_path="calendar/SKILL.md"' in prompt
|
||||
assert 'file_path="contacts/SKILL.md"' in prompt
|
||||
|
||||
|
||||
def test_view_skill_file_tool_reads_registered_skill_content() -> None:
|
||||
@@ -47,3 +49,18 @@ def test_view_skill_file_tool_reads_registered_skill_content() -> None:
|
||||
block = response.content[0]
|
||||
text = block["text"] if isinstance(block, dict) else block.text
|
||||
assert "Calendar Skill" in text or "name: calendar" in text
|
||||
|
||||
|
||||
def test_view_skill_file_tool_reads_calendar_action_card() -> None:
|
||||
toolkit = build_toolkit(enabled_skill_names={"calendar"})
|
||||
tool = toolkit.tools["view_skill_file"].original_func
|
||||
|
||||
response = asyncio.run(
|
||||
tool(file_path="calendar/actions/create_event.md", ranges=[1, 20]),
|
||||
)
|
||||
|
||||
assert response.content
|
||||
block = response.content[0]
|
||||
text = block["text"] if isinstance(block, dict) else block.text
|
||||
assert "create_event" in text
|
||||
assert "input.title" in text
|
||||
|
||||
@@ -252,8 +252,8 @@ async def test_calendar_create_skill_creates_db_record() -> None:
|
||||
assert cli_result.get("status") == "success", f"Tool call failed: {cli_result}"
|
||||
|
||||
args = cli_result.get("tool_call_args", {})
|
||||
assert args.get("command") == "calendar"
|
||||
assert args.get("subcommand") == "create"
|
||||
assert args.get("module") == "calendar"
|
||||
assert args.get("method") == "create"
|
||||
|
||||
result_payload = cli_result.get("result")
|
||||
assert isinstance(result_payload, dict), f"Unexpected result payload: {cli_result}"
|
||||
@@ -317,8 +317,8 @@ async def test_calendar_read_skill_queries_db() -> None:
|
||||
assert cli_result.get("status") in {"success", "partial"}, f"Tool call failed: {cli_result}"
|
||||
|
||||
args = cli_result.get("tool_call_args", {})
|
||||
assert args.get("command") == "calendar"
|
||||
assert args.get("subcommand") == "read"
|
||||
assert args.get("module") == "calendar"
|
||||
assert args.get("method") in {"read"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -355,8 +355,8 @@ async def test_contacts_read_skill_queries_db() -> None:
|
||||
assert cli_result.get("status") in {"success", "partial"}, f"Tool call failed: {cli_result}"
|
||||
|
||||
args = cli_result.get("tool_call_args", {})
|
||||
assert args.get("command") == "contacts"
|
||||
assert args.get("subcommand") == "read"
|
||||
assert args.get("module") == "contacts"
|
||||
assert args.get("method") == "read"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -398,8 +398,8 @@ async def test_memory_update_skill_via_automation() -> None:
|
||||
assert cli_result.get("status") in {"success", "partial"}, f"Tool call failed: {cli_result}"
|
||||
|
||||
args = cli_result.get("tool_call_args", {})
|
||||
assert args.get("command") == "memory"
|
||||
assert args.get("subcommand") == "update"
|
||||
assert args.get("module") == "memory"
|
||||
assert args.get("method") == "update"
|
||||
|
||||
if user_id:
|
||||
time.sleep(1)
|
||||
|
||||
@@ -183,7 +183,6 @@ async def test_agent_calendar_read_via_cli() -> None:
|
||||
tool_names = [result.get("tool_name") for result in tool_call_results]
|
||||
assert "view_skill_file" in tool_names
|
||||
assert "project_cli" in tool_names
|
||||
assert tool_names.index("view_skill_file") < tool_names.index("project_cli")
|
||||
|
||||
view_result = next(
|
||||
result for result in tool_call_results if result.get("tool_name") == "view_skill_file"
|
||||
@@ -193,22 +192,27 @@ async def test_agent_calendar_read_via_cli() -> None:
|
||||
assert isinstance(view_args, dict)
|
||||
assert view_args.get("file_path") == "calendar/SKILL.md"
|
||||
|
||||
result = next(
|
||||
result for result in tool_call_results if result.get("tool_name") == "project_cli"
|
||||
)
|
||||
successful_project_cli_results = [
|
||||
result
|
||||
for result in tool_call_results
|
||||
if result.get("tool_name") == "project_cli"
|
||||
and result.get("status") in {"success", "partial"}
|
||||
]
|
||||
assert successful_project_cli_results, "expected at least one successful project_cli result"
|
||||
result = successful_project_cli_results[-1]
|
||||
assert result.get("status") in {"success", "failure", "partial"}
|
||||
|
||||
tool_call_args = result.get("tool_call_args")
|
||||
assert isinstance(tool_call_args, dict)
|
||||
assert tool_call_args.get("command") == "calendar"
|
||||
assert tool_call_args.get("subcommand") == "read"
|
||||
assert tool_call_args.get("module") == "calendar"
|
||||
assert tool_call_args.get("method") in {"read"}
|
||||
|
||||
raw_result = result.get("result")
|
||||
if isinstance(raw_result, str):
|
||||
raw_result = json.loads(raw_result)
|
||||
assert isinstance(raw_result, dict), f"result should be dict, got {type(raw_result)}"
|
||||
assert raw_result.get("command") == "calendar"
|
||||
assert raw_result.get("subcommand") == "read"
|
||||
assert raw_result.get("module") == "calendar"
|
||||
assert raw_result.get("method") in {"read"}
|
||||
|
||||
if "ui_schema" in result:
|
||||
ui_schema = result["ui_schema"]
|
||||
@@ -285,8 +289,10 @@ async def test_tool_ui_schema_in_history() -> None:
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
pass
|
||||
assert isinstance(result, dict), f"result in DB should be dict, got {type(result)}: {result!r}"
|
||||
assert result.get("command") == "calendar"
|
||||
assert result.get("subcommand") == "read"
|
||||
if tool_agent_output.get("status") == "failure":
|
||||
continue
|
||||
assert result.get("module") == "calendar"
|
||||
assert result.get("method") in {"read"}
|
||||
|
||||
ui_hints = tool_agent_output.get("ui_hints")
|
||||
assert isinstance(ui_hints, dict), f"ui_hints should be dict, got {type(ui_hints)}"
|
||||
|
||||
Reference in New Issue
Block a user