Implemented the next parity slice.

New runtime/code:

  - src/ask_user_runtime.py
  - src/team_runtime.py

  New real tools in src/agent_tools.py:

  - ask_user_question
  - team_create
  - team_delete
  - team_list
  - team_get
  - send_message
  - team_messages
  - notebook_edit
This commit is contained in:
Abdelrahman Abdallah
2026-04-07 03:48:20 +02:00
parent a5629295ac
commit c17c2768eb
17 changed files with 2507 additions and 3 deletions
+35
View File
@@ -224,6 +224,41 @@ class AgentSlashCommandTests(unittest.TestCase):
self.assertIn('profile=staging', ssh_result.final_output)
self.assertIn('connected=False', disconnect_result.final_output)
def test_workflow_and_trigger_commands_render_local_reports(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-workflows.json').write_text(
(
'{"workflows":['
'{"name":"review","description":"Review changes.","steps":["Inspect diff","Summarize findings"]}'
']}'
),
encoding='utf-8',
)
(workspace / '.claw-triggers.json').write_text(
(
'{"triggers":['
'{"trigger_id":"nightly","name":"Nightly","workflow":"review","schedule":"0 0 * * *"}'
']}'
),
encoding='utf-8',
)
agent = LocalCodingAgent(
model_config=ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct'),
runtime_config=AgentRuntimeConfig(cwd=workspace),
)
workflows_result = agent.run('/workflows')
workflow_result = agent.run('/workflow review')
trigger_result = agent.run('/trigger nightly')
trigger_run_result = agent.run('/trigger run nightly')
self.assertIn('# Workflows', workflows_result.final_output)
self.assertIn('review', workflows_result.final_output)
self.assertIn('# Workflow', workflow_result.final_output)
self.assertIn('Review changes', workflow_result.final_output)
self.assertIn('# Remote Trigger', trigger_result.final_output)
self.assertIn('trigger_id=nightly', trigger_result.final_output)
self.assertIn('# Remote Trigger Run', trigger_run_result.final_output)
def test_account_commands_render_and_update_local_account_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
+22
View File
@@ -154,6 +154,28 @@ class MainCliTests(unittest.TestCase):
self.assertEqual(args.provider, 'local-search')
self.assertEqual(args.cwd, '.')
def test_parser_accepts_worktree_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['worktree-exit', '--action', 'remove', '--discard-changes', '--cwd', '.'])
self.assertEqual(args.command, 'worktree-exit')
self.assertEqual(args.action, 'remove')
self.assertTrue(args.discard_changes)
self.assertEqual(args.cwd, '.')
def test_parser_accepts_workflow_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['workflow-run', 'review', '--arguments-json', '{"path":"src"}', '--cwd', '.'])
self.assertEqual(args.command, 'workflow-run')
self.assertEqual(args.workflow_name, 'review')
self.assertEqual(args.cwd, '.')
def test_parser_accepts_remote_trigger_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['trigger-run', 'nightly', '--body-json', '{"depth":"quick"}', '--cwd', '.'])
self.assertEqual(args.command, 'trigger-run')
self.assertEqual(args.trigger_id, 'nightly')
self.assertEqual(args.cwd, '.')
def test_parser_accepts_mcp_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['mcp-tools', '--cwd', '.', '--server', 'remote'])
+84
View File
@@ -0,0 +1,84 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentPermissions, AgentRuntimeConfig
from src.remote_trigger_runtime import RemoteTriggerRuntime
class RemoteTriggerRuntimeTests(unittest.TestCase):
def test_remote_trigger_runtime_discovers_and_runs_trigger(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-triggers.json').write_text(
(
'{"triggers":['
'{"trigger_id":"nightly","name":"Nightly","workflow":"review",'
'"schedule":"0 0 * * *","body":{"depth":"full"}}'
']}'
),
encoding='utf-8',
)
runtime = RemoteTriggerRuntime.from_workspace(workspace)
trigger_report = runtime.render_trigger('nightly')
run_report = runtime.render_run_report('nightly', body={'depth': 'quick'})
self.assertIn('trigger_id=nightly', trigger_report)
self.assertIn('workflow=review', run_report)
self.assertIn('"depth": "quick"', run_report)
def test_remote_trigger_tool_supports_create_update_run(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
runtime = RemoteTriggerRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(
cwd=workspace,
permissions=AgentPermissions(allow_file_write=True),
),
remote_trigger_runtime=runtime,
)
create_result = execute_tool(
default_tool_registry(),
'remote_trigger',
{
'action': 'create',
'body': {
'trigger_id': 'nightly',
'name': 'Nightly',
'workflow': 'review',
'body': {'depth': 'full'},
},
},
context,
)
update_result = execute_tool(
default_tool_registry(),
'remote_trigger',
{
'action': 'update',
'trigger_id': 'nightly',
'body': {'schedule': '0 0 * * *'},
},
context,
)
run_result = execute_tool(
default_tool_registry(),
'remote_trigger',
{
'action': 'run',
'trigger_id': 'nightly',
'body': {'depth': 'quick'},
},
context,
)
self.assertTrue(create_result.ok)
self.assertEqual(create_result.metadata.get('trigger_id'), 'nightly')
self.assertTrue(update_result.ok)
self.assertEqual(update_result.metadata.get('remote_trigger_action'), 'update')
self.assertTrue(run_result.ok)
self.assertIn('# Remote Trigger Run', run_result.content)
+77
View File
@@ -0,0 +1,77 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentRuntimeConfig
from src.workflow_runtime import WorkflowRuntime
class WorkflowRuntimeTests(unittest.TestCase):
def test_workflow_runtime_discovers_and_runs_workflow(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-workflows.json').write_text(
(
'{"workflows":['
'{"name":"review","description":"Review the current patch.",'
'"steps":[{"title":"Inspect diff","detail":"Read {path}"},{"title":"Summarize"}],'
'"prompt":"Review changes under {path}"}'
']}'
),
encoding='utf-8',
)
runtime = WorkflowRuntime.from_workspace(workspace)
rendered = runtime.render_workflow('review')
run_report = runtime.render_run_report('review', arguments={'path': 'src/'})
self.assertIn('Review the current patch', rendered)
self.assertIn('Read src/', run_report)
self.assertIn('Review changes under src/', run_report)
def test_workflow_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-workflows.json').write_text(
(
'{"workflows":['
'{"name":"build","description":"Build the project.",'
'"steps":["Inspect package","Run build"]}'
']}'
),
encoding='utf-8',
)
runtime = WorkflowRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(cwd=workspace),
workflow_runtime=runtime,
)
list_result = execute_tool(
default_tool_registry(),
'workflow_list',
{},
context,
)
get_result = execute_tool(
default_tool_registry(),
'workflow_get',
{'workflow_name': 'build'},
context,
)
run_result = execute_tool(
default_tool_registry(),
'workflow_run',
{'workflow_name': 'build', 'arguments': {'target': 'dist'}},
context,
)
self.assertTrue(list_result.ok)
self.assertIn('build', list_result.content)
self.assertTrue(get_result.ok)
self.assertIn('Build the project', get_result.content)
self.assertTrue(run_result.ok)
self.assertEqual(run_result.metadata.get('action'), 'workflow_run')
self.assertIn('# Workflow Run', run_result.content)
+187
View File
@@ -0,0 +1,187 @@
from __future__ import annotations
import json
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from src.agent_runtime import LocalCodingAgent
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentPermissions, AgentRuntimeConfig, ModelConfig
from src.worktree_runtime import WorktreeRuntime
class _FakeHTTPResponse:
def __init__(self, payload: dict[str, object]) -> None:
self.payload = payload
def read(self) -> bytes:
return json.dumps(self.payload).encode('utf-8')
def __enter__(self) -> '_FakeHTTPResponse':
return self
def __exit__(self, exc_type, exc, tb) -> None:
return None
def _make_urlopen_side_effect(responses: list[dict[str, object]]):
queued = [_FakeHTTPResponse(payload) for payload in responses]
def _fake_urlopen(request_obj, timeout=None): # noqa: ANN001
return queued.pop(0)
return _fake_urlopen
def _init_git_repo(workspace: Path) -> None:
subprocess.run(['git', 'init', '-q'], cwd=workspace, check=True)
subprocess.run(['git', 'config', 'user.email', 'test@example.com'], cwd=workspace, check=True)
subprocess.run(['git', 'config', 'user.name', 'Test User'], cwd=workspace, check=True)
(workspace / 'README.md').write_text('hello\n', encoding='utf-8')
subprocess.run(['git', 'add', 'README.md'], cwd=workspace, check=True)
subprocess.run(['git', 'commit', '-qm', 'init'], cwd=workspace, check=True)
@unittest.skipUnless(shutil.which('git'), 'git is required for worktree tests')
class WorktreeRuntimeTests(unittest.TestCase):
def test_worktree_runtime_enters_and_exits_managed_session(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
_init_git_repo(workspace)
runtime = WorktreeRuntime.from_workspace(workspace)
enter_report = runtime.enter('feature-preview')
worktree_path = Path(enter_report.worktree_path or '')
exit_report = runtime.exit(action='keep')
self.assertTrue(enter_report.active)
self.assertTrue(worktree_path.exists())
self.assertIn('feature-preview', enter_report.worktree_branch or '')
self.assertFalse(exit_report.active)
self.assertEqual(exit_report.original_cwd, str(workspace))
def test_worktree_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
_init_git_repo(workspace)
runtime = WorktreeRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(
cwd=workspace,
permissions=AgentPermissions(allow_file_write=True),
),
worktree_runtime=runtime,
)
enter_result = execute_tool(
default_tool_registry(),
'worktree_enter',
{'name': 'preview'},
context,
)
status_result = execute_tool(
default_tool_registry(),
'worktree_status',
{},
context,
)
exit_result = execute_tool(
default_tool_registry(),
'worktree_exit',
{'action': 'remove', 'discard_changes': True},
context,
)
self.assertTrue(enter_result.ok)
self.assertIn('preview', enter_result.content)
self.assertEqual(enter_result.metadata.get('action'), 'worktree_enter')
self.assertTrue(status_result.ok)
self.assertIn('Active managed worktree: True', status_result.content)
self.assertTrue(exit_result.ok)
self.assertEqual(exit_result.metadata.get('action'), 'worktree_exit')
def test_agent_switches_cwd_after_worktree_enter(self) -> None:
responses = [
{
'choices': [
{
'message': {
'role': 'assistant',
'content': 'Entering worktree.',
'tool_calls': [
{
'id': 'call_enter',
'type': 'function',
'function': {
'name': 'worktree_enter',
'arguments': '{"name":"preview"}',
},
}
],
},
'finish_reason': 'tool_calls',
}
],
'usage': {'prompt_tokens': 8, 'completion_tokens': 2},
},
{
'choices': [
{
'message': {
'role': 'assistant',
'content': 'Writing inside the worktree.',
'tool_calls': [
{
'id': 'call_write',
'type': 'function',
'function': {
'name': 'write_file',
'arguments': '{"path":"note.txt","content":"from worktree\\n"}',
},
}
],
},
'finish_reason': 'tool_calls',
}
],
'usage': {'prompt_tokens': 8, 'completion_tokens': 2},
},
{
'choices': [
{
'message': {
'role': 'assistant',
'content': 'done',
},
'finish_reason': 'stop',
}
],
'usage': {'prompt_tokens': 6, 'completion_tokens': 1},
},
]
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
_init_git_repo(workspace)
with patch(
'src.openai_compat.request.urlopen',
side_effect=_make_urlopen_side_effect(responses),
):
agent = LocalCodingAgent(
model_config=ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct'),
runtime_config=AgentRuntimeConfig(
cwd=workspace,
permissions=AgentPermissions(allow_file_write=True),
),
)
result = agent.run('Use a worktree and write a file there')
runtime = WorktreeRuntime.from_workspace(workspace)
assert runtime.active_session is not None
worktree_path = Path(runtime.active_session.worktree_path)
self.assertEqual(result.final_output, 'done')
self.assertFalse((workspace / 'note.txt').exists())
self.assertTrue((worktree_path / 'note.txt').exists())
self.assertEqual(agent.runtime_config.cwd, worktree_path.resolve())