add mcp and online search

This commit is contained in:
Abdelrahman Abdallah
2026-04-05 02:35:49 +02:00
parent 3f31cee395
commit 783145fe6a
38 changed files with 8114 additions and 261 deletions
+344 -53
View File
@@ -9,6 +9,7 @@ import json
from typing import Callable
from .background_runtime import BackgroundSessionRuntime, build_background_worker_command
from .account_runtime import AccountRuntime
from .agent_runtime import LocalCodingAgent
from .agent_types import (
AgentPermissions,
@@ -21,12 +22,21 @@ from .agent_types import (
from .bootstrap_graph import build_bootstrap_graph
from .command_graph import build_command_graph
from .commands import execute_command, get_command, get_commands, render_command_index
from .direct_modes import run_deep_link, run_direct_connect
from .config_runtime import ConfigRuntime
from .mcp_runtime import MCPRuntime
from .parity_audit import run_parity_audit
from .permissions import ToolPermissionContext
from .port_manifest import build_port_manifest
from .query_engine import QueryEnginePort
from .remote_runtime import run_remote_mode, run_ssh_mode, run_teleport_mode
from .remote_runtime import (
RemoteRuntime,
run_deep_link_mode,
run_direct_connect_mode,
run_remote_mode,
run_ssh_mode,
run_teleport_mode,
)
from .search_runtime import SearchRuntime
from .runtime import PortRuntime
from .session_store import (
StoredAgentSession,
@@ -248,6 +258,58 @@ def _add_agent_resume_args(parser: argparse.ArgumentParser) -> None:
parser.add_argument('--scratchpad-root')
def _launch_background_agent(args: argparse.Namespace) -> int:
background_runtime = BackgroundSessionRuntime()
background_id = background_runtime.create_id()
forwarded_args: list[str] = []
_append_agent_forwarded_args(forwarded_args, args, include_backend=True)
forwarded_args.extend(['--background-root', str(background_runtime.root)])
command = build_background_worker_command(
background_id=background_id,
prompt=args.prompt,
forwarded_args=forwarded_args,
)
record = background_runtime.launch(
command,
prompt=args.prompt,
workspace_cwd=Path(args.cwd).resolve(),
model=args.model,
background_id=background_id,
process_cwd=Path(__file__).resolve().parent.parent,
)
print('# Background Session')
print(f'background_id={record.background_id}')
print(f'pid={record.pid}')
print(f'log_path={record.log_path}')
print(f'record_path={record.record_path}')
return 0
def _run_background_worker(args: argparse.Namespace) -> int:
background_runtime = BackgroundSessionRuntime(Path(args.background_root))
exit_code = 1
stop_reason = 'worker_failed'
session_id = None
session_path = None
try:
agent = _build_agent(args)
result = agent.run(args.prompt)
_print_agent_result(result, show_transcript=args.show_transcript)
exit_code = 0
stop_reason = result.stop_reason or 'completed'
session_id = result.session_id
session_path = result.session_path
return 0
finally:
background_runtime.mark_finished(
args.background_id,
exit_code=exit_code,
stop_reason=stop_reason,
session_id=session_id,
session_path=session_path,
)
def _build_resumed_agent(args: argparse.Namespace) -> tuple[LocalCodingAgent, StoredAgentSession]:
stored_session = load_agent_session(args.session_id)
model_config = deserialize_model_config(stored_session.model_config)
@@ -519,14 +581,86 @@ def build_parser() -> argparse.ArgumentParser:
remote_parser = subparsers.add_parser('remote-mode', help='simulate remote-control runtime branching')
remote_parser.add_argument('target')
remote_parser.add_argument('--cwd', default='.')
ssh_parser = subparsers.add_parser('ssh-mode', help='simulate SSH runtime branching')
ssh_parser.add_argument('target')
ssh_parser.add_argument('--cwd', default='.')
teleport_parser = subparsers.add_parser('teleport-mode', help='simulate teleport runtime branching')
teleport_parser.add_argument('target')
teleport_parser.add_argument('--cwd', default='.')
direct_parser = subparsers.add_parser('direct-connect-mode', help='simulate direct-connect runtime branching')
direct_parser.add_argument('target')
direct_parser.add_argument('--cwd', default='.')
deep_link_parser = subparsers.add_parser('deep-link-mode', help='simulate deep-link runtime branching')
deep_link_parser.add_argument('target')
deep_link_parser.add_argument('--cwd', default='.')
remote_status_parser = subparsers.add_parser('remote-status', help='show local remote runtime status')
remote_status_parser.add_argument('--cwd', default='.')
remote_profiles_parser = subparsers.add_parser('remote-profiles', help='list configured local remote profiles')
remote_profiles_parser.add_argument('--cwd', default='.')
remote_profiles_parser.add_argument('--query')
remote_disconnect_parser = subparsers.add_parser('remote-disconnect', help='disconnect the active local remote target')
remote_disconnect_parser.add_argument('--cwd', default='.')
account_status_parser = subparsers.add_parser('account-status', help='show local account runtime status')
account_status_parser.add_argument('--cwd', default='.')
account_profiles_parser = subparsers.add_parser('account-profiles', help='list configured local account profiles')
account_profiles_parser.add_argument('--cwd', default='.')
account_profiles_parser.add_argument('--query')
account_login_parser = subparsers.add_parser('account-login', help='activate a local account profile or ephemeral identity')
account_login_parser.add_argument('target')
account_login_parser.add_argument('--provider')
account_login_parser.add_argument('--auth-mode')
account_login_parser.add_argument('--cwd', default='.')
account_logout_parser = subparsers.add_parser('account-logout', help='clear the active local account session')
account_logout_parser.add_argument('--cwd', default='.')
search_status_parser = subparsers.add_parser('search-status', help='show local search runtime status')
search_status_parser.add_argument('--cwd', default='.')
search_status_parser.add_argument('--provider')
search_providers_parser = subparsers.add_parser('search-providers', help='list configured local search providers')
search_providers_parser.add_argument('--cwd', default='.')
search_providers_parser.add_argument('--query')
search_activate_parser = subparsers.add_parser('search-activate', help='set the active local search provider')
search_activate_parser.add_argument('provider')
search_activate_parser.add_argument('--cwd', default='.')
search_parser = subparsers.add_parser('search', help='run a real web search against the configured local search runtime')
search_parser.add_argument('query')
search_parser.add_argument('--cwd', default='.')
search_parser.add_argument('--provider')
search_parser.add_argument('--max-results', type=int, default=5)
search_parser.add_argument('--domain', action='append', default=[])
mcp_status_parser = subparsers.add_parser('mcp-status', help='show local MCP runtime status')
mcp_status_parser.add_argument('--cwd', default='.')
mcp_resources_parser = subparsers.add_parser('mcp-resources', help='list MCP resources discovered through local manifests and transport-backed servers')
mcp_resources_parser.add_argument('--cwd', default='.')
mcp_resources_parser.add_argument('--query')
mcp_resource_parser = subparsers.add_parser('mcp-resource', help='read an MCP resource by URI')
mcp_resource_parser.add_argument('uri')
mcp_resource_parser.add_argument('--cwd', default='.')
mcp_tools_parser = subparsers.add_parser('mcp-tools', help='list MCP tools exposed by configured MCP servers')
mcp_tools_parser.add_argument('--cwd', default='.')
mcp_tools_parser.add_argument('--query')
mcp_tools_parser.add_argument('--server')
mcp_call_tool_parser = subparsers.add_parser('mcp-call-tool', help='call an MCP tool exposed by a configured MCP server')
mcp_call_tool_parser.add_argument('tool_name')
mcp_call_tool_parser.add_argument('--arguments-json', default='{}')
mcp_call_tool_parser.add_argument('--server')
mcp_call_tool_parser.add_argument('--cwd', default='.')
config_status_parser = subparsers.add_parser('config-status', help='show local workspace config runtime summary')
config_status_parser.add_argument('--cwd', default='.')
config_effective_parser = subparsers.add_parser('config-effective', help='render the merged effective local workspace config')
config_effective_parser.add_argument('--cwd', default='.')
config_source_parser = subparsers.add_parser('config-source', help='render a specific local config source')
config_source_parser.add_argument('source')
config_source_parser.add_argument('--cwd', default='.')
config_get_parser = subparsers.add_parser('config-get', help='read a local config value by dotted key path')
config_get_parser.add_argument('key_path')
config_get_parser.add_argument('--source')
config_get_parser.add_argument('--cwd', default='.')
config_set_parser = subparsers.add_parser('config-set', help='write a local config value by dotted key path')
config_set_parser.add_argument('key_path')
config_set_parser.add_argument('value_json')
config_set_parser.add_argument('--source', default='local')
config_set_parser.add_argument('--cwd', default='.')
show_command = subparsers.add_parser('show-command', help='show one mirrored command entry by exact name')
show_command.add_argument('name')
@@ -575,6 +709,38 @@ def build_parser() -> argparse.ArgumentParser:
kill_parser = subparsers.add_parser('agent-kill', help='stop a local background agent session')
kill_parser.add_argument('background_id')
daemon_parser = subparsers.add_parser('daemon', help='manage local daemon-style background agent sessions')
daemon_subparsers = daemon_parser.add_subparsers(dest='daemon_command')
daemon_subparsers.required = True
daemon_start_parser = daemon_subparsers.add_parser('start', help='launch a local daemon-style background agent session')
daemon_start_parser.add_argument('prompt')
daemon_start_parser.add_argument('--max-turns', type=int, default=12)
daemon_start_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(daemon_start_parser, include_backend=True)
daemon_worker_parser = daemon_subparsers.add_parser('worker', help=argparse.SUPPRESS)
daemon_worker_parser.add_argument('background_id')
daemon_worker_parser.add_argument('prompt')
daemon_worker_parser.add_argument('--background-root', required=True)
daemon_worker_parser.add_argument('--max-turns', type=int, default=12)
daemon_worker_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(daemon_worker_parser, include_backend=True)
daemon_ps_parser = daemon_subparsers.add_parser('ps', help='list local daemon-style background sessions')
daemon_ps_parser.add_argument('--tail', type=int, default=None)
daemon_logs_parser = daemon_subparsers.add_parser('logs', help='show logs for a local daemon-style background session')
daemon_logs_parser.add_argument('background_id')
daemon_logs_parser.add_argument('--tail', type=int, default=None)
daemon_attach_parser = daemon_subparsers.add_parser('attach', help='show the current output snapshot for a local daemon-style background session')
daemon_attach_parser.add_argument('background_id')
daemon_attach_parser.add_argument('--tail', type=int, default=None)
daemon_kill_parser = daemon_subparsers.add_parser('kill', help='stop a local daemon-style background session')
daemon_kill_parser.add_argument('background_id')
chat_parser = subparsers.add_parser('agent-chat', help='run an interactive Python local-model chat loop')
chat_parser.add_argument('prompt', nargs='?')
chat_parser.add_argument('--resume-session-id')
@@ -687,19 +853,155 @@ def main(argv: list[str] | None = None) -> int:
print(f'{session.session_id}\n{len(session.messages)} messages\nin={session.input_tokens} out={session.output_tokens}')
return 0
if args.command == 'remote-mode':
print(run_remote_mode(args.target).as_text())
print(run_remote_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'ssh-mode':
print(run_ssh_mode(args.target).as_text())
print(run_ssh_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'teleport-mode':
print(run_teleport_mode(args.target).as_text())
print(run_teleport_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'direct-connect-mode':
print(run_direct_connect(args.target).as_text())
print(run_direct_connect_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'deep-link-mode':
print(run_deep_link(args.target).as_text())
print(run_deep_link_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'remote-status':
runtime = RemoteRuntime.from_workspace(Path(args.cwd).resolve())
print('# Remote')
print()
print(runtime.render_summary())
return 0
if args.command == 'remote-profiles':
runtime = RemoteRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_profiles_index(query=args.query))
return 0
if args.command == 'remote-disconnect':
runtime = RemoteRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.disconnect().as_text())
return 0
if args.command == 'account-status':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print('# Account')
print()
print(runtime.render_summary())
return 0
if args.command == 'account-profiles':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_profiles_index(query=args.query))
return 0
if args.command == 'account-login':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print(
runtime.login(
args.target,
provider=args.provider,
auth_mode=args.auth_mode,
).as_text()
)
return 0
if args.command == 'account-logout':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.logout().as_text())
return 0
if args.command == 'search-status':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
if args.provider:
print(runtime.render_provider(args.provider))
else:
print('# Search')
print()
print(runtime.render_summary())
return 0
if args.command == 'search-providers':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_providers_index(query=args.query))
return 0
if args.command == 'search-activate':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
try:
report = runtime.activate_provider(args.provider)
except KeyError:
print(f'Unknown search provider: {args.provider}')
return 1
print(report.as_text())
return 0
if args.command == 'search':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
try:
output = runtime.render_search_results(
args.query,
provider_name=args.provider,
max_results=args.max_results,
domains=tuple(args.domain),
)
except (KeyError, LookupError, OSError, ValueError) as exc:
print(f'Search failed: {exc}')
return 1
print(output)
return 0
if args.command == 'mcp-status':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print('# MCP')
print()
print(runtime.render_summary())
return 0
if args.command == 'mcp-resources':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_resource_index(query=args.query))
return 0
if args.command == 'mcp-resource':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_resource(args.uri))
return 0
if args.command == 'mcp-tools':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_tool_index(query=args.query, server_name=args.server))
return 0
if args.command == 'mcp-call-tool':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
arguments = json.loads(args.arguments_json)
if not isinstance(arguments, dict):
print('arguments-json must decode to a JSON object')
return 1
print(
runtime.render_tool_call(
args.tool_name,
arguments=arguments,
server_name=args.server,
)
)
return 0
if args.command == 'config-status':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print('# Config')
print()
print(runtime.render_summary())
return 0
if args.command == 'config-effective':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_effective_config())
return 0
if args.command == 'config-source':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_source(args.source))
return 0
if args.command == 'config-get':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_value(args.key_path, source=args.source))
return 0
if args.command == 'config-set':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
value = json.loads(args.value_json)
mutation = runtime.set_value(args.key_path, value, source=args.source)
print('# Config')
print()
print(f'source={mutation.source_name}')
print(f'key_path={mutation.key_path}')
print(f'store_path={mutation.store_path}')
print(f'effective_key_count={mutation.effective_key_count}')
print(runtime.render_value(args.key_path))
return 0
if args.command == 'show-command':
module = get_command(args.name)
@@ -729,53 +1031,9 @@ def main(argv: list[str] | None = None) -> int:
_print_agent_result(result, show_transcript=args.show_transcript)
return 0
if args.command == 'agent-bg':
background_runtime = BackgroundSessionRuntime()
background_id = background_runtime.create_id()
forwarded_args: list[str] = []
_append_agent_forwarded_args(forwarded_args, args, include_backend=True)
forwarded_args.extend(['--background-root', str(background_runtime.root)])
command = build_background_worker_command(
background_id=background_id,
prompt=args.prompt,
forwarded_args=forwarded_args,
)
record = background_runtime.launch(
command,
prompt=args.prompt,
workspace_cwd=Path(args.cwd).resolve(),
model=args.model,
background_id=background_id,
process_cwd=Path(__file__).resolve().parent.parent,
)
print('# Background Session')
print(f'background_id={record.background_id}')
print(f'pid={record.pid}')
print(f'log_path={record.log_path}')
print(f'record_path={record.record_path}')
return 0
return _launch_background_agent(args)
if args.command == 'agent-bg-worker':
background_runtime = BackgroundSessionRuntime(Path(args.background_root))
exit_code = 1
stop_reason = 'worker_failed'
session_id = None
session_path = None
try:
agent = _build_agent(args)
result = agent.run(args.prompt)
_print_agent_result(result, show_transcript=args.show_transcript)
exit_code = 0
stop_reason = result.stop_reason or 'completed'
session_id = result.session_id
session_path = result.session_path
return 0
finally:
background_runtime.mark_finished(
args.background_id,
exit_code=exit_code,
stop_reason=stop_reason,
session_id=session_id,
session_path=session_path,
)
return _run_background_worker(args)
if args.command == 'agent-ps':
print(BackgroundSessionRuntime().render_ps())
return 0
@@ -804,6 +1062,39 @@ def main(argv: list[str] | None = None) -> int:
if record.exit_code is not None:
print(f'exit_code={record.exit_code}')
return 0
if args.command == 'daemon':
if args.daemon_command == 'start':
return _launch_background_agent(args)
if args.daemon_command == 'worker':
return _run_background_worker(args)
if args.daemon_command == 'ps':
print(BackgroundSessionRuntime().render_ps())
return 0
if args.daemon_command == 'logs':
print(
BackgroundSessionRuntime().render_logs(
args.background_id,
tail=args.tail,
)
)
return 0
if args.daemon_command == 'attach':
print(
BackgroundSessionRuntime().render_attach(
args.background_id,
tail=args.tail,
)
)
return 0
if args.daemon_command == 'kill':
record = BackgroundSessionRuntime().kill(args.background_id)
print('# Background Session')
print(f'background_id={record.background_id}')
print(f'status={record.status}')
print(f'pid={record.pid}')
if record.exit_code is not None:
print(f'exit_code={record.exit_code}')
return 0
if args.command == 'agent-chat':
agent = _build_agent(args)
return _run_agent_chat_loop(