b5e5824a56
Core changes:
- Added claw-code/src/token_budget.py for projected prompt size, chat-framing overhead, output reserve, and soft/hard input limits.
- Wired preflight prompt-length validation and auto-compact/context collapse into claw-code/src/agent_runtime.py.
- Extended claw-code/src/compact.py so compaction reports usage back to the runtime.
- Added inspection surfaces in claw-code/src/agent_slash_commands.py and claw-code/src/main.py:
- /token-budget and /budget
- token-budget
- Hardened claw-code/src/tokenizer_runtime.py so arbitrary simple model names fall back cleanly instead of trying a slow Transformers
lookup.
- Exported the new helpers in claw-code/src/__init__.py.
Docs and tracking:
- Updated claw-code/PARITY_CHECKLIST.md to mark prompt-length validation, token-budget calculation, and auto-compact/context collapse as
done.
- Updated claw-code/README.md and claw-code/TESTING_GUIDE.md with the new commands and behavior.
Tests:
- Added claw-code/tests/test_token_budget.py.
- Updated claw-code/tests/test_agent_runtime.py, claw-code/tests/test_agent_slash_commands.py, claw-code/tests/test_main.py, and claw-code/
tests/test_agent_context_usage.py.
- Verified with:
- /data/fs201059/aa17626/miniconda3/bin/python3 -m compileall src tests
- /data/fs201059/aa17626/miniconda3/bin/python3 -m unittest -v tests.test_token_budget
tests.test_agent_runtime.AgentRuntimeTests.test_agent_rejects_prompt_before_backend_when_preflight_input_budget_is_exceeded
tests.test_agent_runtime.AgentRuntimeTests.test_agent_auto_compacts_context_before_next_model_call tests.test_agent_slash_commands
tests.test_main tests.test_compact tests.test_tokenizer_runtime tests.test_agent_context_usage
- Result: 71 tests, OK
1517 lines
69 KiB
Python
1517 lines
69 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from dataclasses import replace
|
|
import json
|
|
from typing import Callable
|
|
|
|
from .background_runtime import BackgroundSessionRuntime, build_background_worker_command
|
|
from .account_runtime import AccountRuntime
|
|
from .ask_user_runtime import AskUserRuntime
|
|
from .agent_runtime import LocalCodingAgent
|
|
from .agent_types import (
|
|
AgentPermissions,
|
|
AgentRuntimeConfig,
|
|
BudgetConfig,
|
|
ModelConfig,
|
|
ModelPricing,
|
|
OutputSchemaConfig,
|
|
)
|
|
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 .config_runtime import ConfigRuntime
|
|
from .lsp_runtime import LSPRuntime
|
|
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 (
|
|
RemoteRuntime,
|
|
run_deep_link_mode,
|
|
run_direct_connect_mode,
|
|
run_remote_mode,
|
|
run_ssh_mode,
|
|
run_teleport_mode,
|
|
)
|
|
from .remote_trigger_runtime import RemoteTriggerRuntime
|
|
from .search_runtime import SearchRuntime
|
|
from .team_runtime import TeamRuntime
|
|
from .task_runtime import TaskRuntime
|
|
from .workflow_runtime import WorkflowRuntime
|
|
from .worktree_runtime import WorktreeRuntime
|
|
from .runtime import PortRuntime
|
|
from .session_store import (
|
|
StoredAgentSession,
|
|
deserialize_model_config,
|
|
deserialize_runtime_config,
|
|
load_agent_session,
|
|
load_session,
|
|
)
|
|
from .setup import run_setup
|
|
from .tool_pool import assemble_tool_pool
|
|
from .tools import execute_tool, get_tool, get_tools, render_tool_index
|
|
|
|
|
|
def _add_agent_common_args(parser: argparse.ArgumentParser, *, include_backend: bool) -> None:
|
|
parser.add_argument('--model', default=os.environ.get('OPENAI_MODEL', 'Qwen/Qwen3-Coder-30B-A3B-Instruct'))
|
|
if include_backend:
|
|
parser.add_argument('--base-url', default=os.environ.get('OPENAI_BASE_URL', 'http://127.0.0.1:8000/v1'))
|
|
parser.add_argument('--api-key', default=os.environ.get('OPENAI_API_KEY', 'local-token'))
|
|
parser.add_argument('--temperature', type=float, default=0.0)
|
|
parser.add_argument('--timeout-seconds', type=float, default=120.0)
|
|
parser.add_argument('--input-cost-per-million', type=float, default=0.0)
|
|
parser.add_argument('--output-cost-per-million', type=float, default=0.0)
|
|
parser.add_argument('--cwd', default='.')
|
|
parser.add_argument('--add-dir', action='append', default=[])
|
|
parser.add_argument('--disable-claude-md', action='store_true')
|
|
parser.add_argument('--allow-write', action='store_true')
|
|
parser.add_argument('--allow-shell', action='store_true')
|
|
parser.add_argument('--unsafe', action='store_true')
|
|
parser.add_argument('--stream', action='store_true')
|
|
parser.add_argument('--auto-snip-threshold', type=int)
|
|
parser.add_argument('--auto-compact-threshold', type=int)
|
|
parser.add_argument('--compact-preserve-messages', type=int, default=4)
|
|
parser.add_argument('--max-total-tokens', type=int)
|
|
parser.add_argument('--max-input-tokens', type=int)
|
|
parser.add_argument('--max-output-tokens', type=int)
|
|
parser.add_argument('--max-reasoning-tokens', type=int)
|
|
parser.add_argument('--max-budget-usd', type=float)
|
|
parser.add_argument('--max-tool-calls', type=int)
|
|
parser.add_argument('--max-delegated-tasks', type=int)
|
|
parser.add_argument('--max-model-calls', type=int)
|
|
parser.add_argument('--max-session-turns', type=int)
|
|
parser.add_argument('--response-schema-file')
|
|
parser.add_argument('--response-schema-name')
|
|
parser.add_argument('--response-schema-strict', action='store_true')
|
|
parser.add_argument('--scratchpad-root')
|
|
parser.add_argument('--system-prompt')
|
|
parser.add_argument('--append-system-prompt')
|
|
parser.add_argument('--override-system-prompt')
|
|
|
|
|
|
def _build_runtime_config(args: argparse.Namespace) -> AgentRuntimeConfig:
|
|
return AgentRuntimeConfig(
|
|
cwd=Path(args.cwd).resolve(),
|
|
max_turns=getattr(args, 'max_turns', 12),
|
|
permissions=AgentPermissions(
|
|
allow_file_write=args.allow_write,
|
|
allow_shell_commands=args.allow_shell,
|
|
allow_destructive_shell_commands=args.unsafe,
|
|
),
|
|
stream_model_responses=bool(getattr(args, 'stream', False)),
|
|
auto_snip_threshold_tokens=getattr(args, 'auto_snip_threshold', None),
|
|
auto_compact_threshold_tokens=getattr(args, 'auto_compact_threshold', None),
|
|
compact_preserve_messages=max(0, int(getattr(args, 'compact_preserve_messages', 4))),
|
|
additional_working_directories=tuple(Path(path).resolve() for path in args.add_dir),
|
|
disable_claude_md_discovery=args.disable_claude_md,
|
|
budget_config=BudgetConfig(
|
|
max_total_tokens=getattr(args, 'max_total_tokens', None),
|
|
max_input_tokens=getattr(args, 'max_input_tokens', None),
|
|
max_output_tokens=getattr(args, 'max_output_tokens', None),
|
|
max_reasoning_tokens=getattr(args, 'max_reasoning_tokens', None),
|
|
max_total_cost_usd=getattr(args, 'max_budget_usd', None),
|
|
max_tool_calls=getattr(args, 'max_tool_calls', None),
|
|
max_delegated_tasks=getattr(args, 'max_delegated_tasks', None),
|
|
max_model_calls=getattr(args, 'max_model_calls', None),
|
|
max_session_turns=getattr(args, 'max_session_turns', None),
|
|
),
|
|
output_schema=_load_output_schema_config(args),
|
|
session_directory=(Path('.port_sessions') / 'agent').resolve(),
|
|
scratchpad_root=(
|
|
Path(getattr(args, 'scratchpad_root')).resolve()
|
|
if getattr(args, 'scratchpad_root', None)
|
|
else (Path('.port_sessions') / 'scratchpad').resolve()
|
|
),
|
|
)
|
|
|
|
|
|
def _build_model_config(args: argparse.Namespace) -> ModelConfig:
|
|
return ModelConfig(
|
|
model=args.model,
|
|
base_url=getattr(args, 'base_url', os.environ.get('OPENAI_BASE_URL', 'http://127.0.0.1:8000/v1')),
|
|
api_key=getattr(args, 'api_key', os.environ.get('OPENAI_API_KEY', 'local-token')),
|
|
temperature=getattr(args, 'temperature', 0.0),
|
|
timeout_seconds=getattr(args, 'timeout_seconds', 120.0),
|
|
pricing=ModelPricing(
|
|
input_cost_per_million_tokens_usd=float(
|
|
getattr(args, 'input_cost_per_million', 0.0) or 0.0
|
|
),
|
|
output_cost_per_million_tokens_usd=float(
|
|
getattr(args, 'output_cost_per_million', 0.0) or 0.0
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
def _load_output_schema_config(args: argparse.Namespace) -> OutputSchemaConfig | None:
|
|
schema_file = getattr(args, 'response_schema_file', None)
|
|
if not schema_file:
|
|
return None
|
|
payload = json.loads(Path(schema_file).read_text(encoding='utf-8'))
|
|
if not isinstance(payload, dict):
|
|
raise ValueError('response schema file must contain a top-level JSON object')
|
|
name = getattr(args, 'response_schema_name', None) or Path(schema_file).stem
|
|
return OutputSchemaConfig(
|
|
name=name,
|
|
schema=payload,
|
|
strict=bool(getattr(args, 'response_schema_strict', False)),
|
|
)
|
|
|
|
|
|
def _build_agent(args: argparse.Namespace) -> LocalCodingAgent:
|
|
return LocalCodingAgent(
|
|
model_config=_build_model_config(args),
|
|
runtime_config=_build_runtime_config(args),
|
|
custom_system_prompt=args.system_prompt,
|
|
append_system_prompt=args.append_system_prompt,
|
|
override_system_prompt=args.override_system_prompt,
|
|
)
|
|
|
|
|
|
def _append_agent_forwarded_args(
|
|
command: list[str],
|
|
args: argparse.Namespace,
|
|
*,
|
|
include_backend: bool,
|
|
) -> None:
|
|
command.extend(['--cwd', str(args.cwd)])
|
|
command.extend(['--max-turns', str(getattr(args, 'max_turns', 12))])
|
|
if include_backend:
|
|
command.extend(['--model', str(args.model)])
|
|
command.extend(['--base-url', str(args.base_url)])
|
|
command.extend(['--api-key', str(args.api_key)])
|
|
command.extend(['--temperature', str(args.temperature)])
|
|
command.extend(['--timeout-seconds', str(args.timeout_seconds)])
|
|
command.extend(['--input-cost-per-million', str(args.input_cost_per_million)])
|
|
command.extend(['--output-cost-per-million', str(args.output_cost_per_million)])
|
|
else:
|
|
command.extend(['--model', str(args.model)])
|
|
for path in getattr(args, 'add_dir', []):
|
|
command.extend(['--add-dir', str(path)])
|
|
for flag in (
|
|
('--disable-claude-md', getattr(args, 'disable_claude_md', False)),
|
|
('--allow-write', getattr(args, 'allow_write', False)),
|
|
('--allow-shell', getattr(args, 'allow_shell', False)),
|
|
('--unsafe', getattr(args, 'unsafe', False)),
|
|
('--stream', getattr(args, 'stream', False)),
|
|
('--show-transcript', getattr(args, 'show_transcript', False)),
|
|
(
|
|
'--response-schema-strict',
|
|
getattr(args, 'response_schema_strict', False),
|
|
),
|
|
):
|
|
if flag[1]:
|
|
command.append(flag[0])
|
|
for name, value in (
|
|
('--auto-snip-threshold', getattr(args, 'auto_snip_threshold', None)),
|
|
('--auto-compact-threshold', getattr(args, 'auto_compact_threshold', None)),
|
|
('--compact-preserve-messages', getattr(args, 'compact_preserve_messages', None)),
|
|
('--max-total-tokens', getattr(args, 'max_total_tokens', None)),
|
|
('--max-input-tokens', getattr(args, 'max_input_tokens', None)),
|
|
('--max-output-tokens', getattr(args, 'max_output_tokens', None)),
|
|
('--max-reasoning-tokens', getattr(args, 'max_reasoning_tokens', None)),
|
|
('--max-budget-usd', getattr(args, 'max_budget_usd', None)),
|
|
('--max-tool-calls', getattr(args, 'max_tool_calls', None)),
|
|
('--max-delegated-tasks', getattr(args, 'max_delegated_tasks', None)),
|
|
('--max-model-calls', getattr(args, 'max_model_calls', None)),
|
|
('--max-session-turns', getattr(args, 'max_session_turns', None)),
|
|
('--response-schema-file', getattr(args, 'response_schema_file', None)),
|
|
('--response-schema-name', getattr(args, 'response_schema_name', None)),
|
|
('--scratchpad-root', getattr(args, 'scratchpad_root', None)),
|
|
('--system-prompt', getattr(args, 'system_prompt', None)),
|
|
('--append-system-prompt', getattr(args, 'append_system_prompt', None)),
|
|
('--override-system-prompt', getattr(args, 'override_system_prompt', None)),
|
|
):
|
|
if value is not None:
|
|
command.extend([name, str(value)])
|
|
|
|
|
|
def _add_agent_resume_args(parser: argparse.ArgumentParser) -> None:
|
|
parser.add_argument('session_id')
|
|
parser.add_argument('prompt')
|
|
parser.add_argument('--max-turns', type=int)
|
|
parser.add_argument('--show-transcript', action='store_true')
|
|
parser.add_argument('--model')
|
|
parser.add_argument('--base-url')
|
|
parser.add_argument('--api-key')
|
|
parser.add_argument('--temperature', type=float)
|
|
parser.add_argument('--timeout-seconds', type=float)
|
|
parser.add_argument('--input-cost-per-million', type=float)
|
|
parser.add_argument('--output-cost-per-million', type=float)
|
|
parser.add_argument('--allow-write', action='store_true')
|
|
parser.add_argument('--allow-shell', action='store_true')
|
|
parser.add_argument('--unsafe', action='store_true')
|
|
parser.add_argument('--stream', action='store_true')
|
|
parser.add_argument('--auto-snip-threshold', type=int)
|
|
parser.add_argument('--auto-compact-threshold', type=int)
|
|
parser.add_argument('--compact-preserve-messages', type=int)
|
|
parser.add_argument('--max-total-tokens', type=int)
|
|
parser.add_argument('--max-input-tokens', type=int)
|
|
parser.add_argument('--max-output-tokens', type=int)
|
|
parser.add_argument('--max-reasoning-tokens', type=int)
|
|
parser.add_argument('--max-budget-usd', type=float)
|
|
parser.add_argument('--max-tool-calls', type=int)
|
|
parser.add_argument('--max-delegated-tasks', type=int)
|
|
parser.add_argument('--max-model-calls', type=int)
|
|
parser.add_argument('--max-session-turns', type=int)
|
|
parser.add_argument('--response-schema-file')
|
|
parser.add_argument('--response-schema-name')
|
|
parser.add_argument('--response-schema-strict', action='store_true')
|
|
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)
|
|
runtime_config = deserialize_runtime_config(stored_session.runtime_config)
|
|
|
|
if args.model:
|
|
model_config = replace(model_config, model=args.model)
|
|
if args.base_url:
|
|
model_config = replace(model_config, base_url=args.base_url)
|
|
if args.api_key:
|
|
model_config = replace(model_config, api_key=args.api_key)
|
|
if args.temperature is not None:
|
|
model_config = replace(model_config, temperature=args.temperature)
|
|
if args.timeout_seconds is not None:
|
|
model_config = replace(model_config, timeout_seconds=args.timeout_seconds)
|
|
if args.input_cost_per_million is not None or args.output_cost_per_million is not None:
|
|
model_config = replace(
|
|
model_config,
|
|
pricing=replace(
|
|
model_config.pricing,
|
|
input_cost_per_million_tokens_usd=(
|
|
args.input_cost_per_million
|
|
if args.input_cost_per_million is not None
|
|
else model_config.pricing.input_cost_per_million_tokens_usd
|
|
),
|
|
output_cost_per_million_tokens_usd=(
|
|
args.output_cost_per_million
|
|
if args.output_cost_per_million is not None
|
|
else model_config.pricing.output_cost_per_million_tokens_usd
|
|
),
|
|
),
|
|
)
|
|
|
|
if args.max_turns is not None:
|
|
runtime_config = replace(runtime_config, max_turns=args.max_turns)
|
|
if args.allow_write or args.allow_shell or args.unsafe:
|
|
runtime_config = replace(
|
|
runtime_config,
|
|
permissions=AgentPermissions(
|
|
allow_file_write=runtime_config.permissions.allow_file_write or args.allow_write,
|
|
allow_shell_commands=runtime_config.permissions.allow_shell_commands or args.allow_shell,
|
|
allow_destructive_shell_commands=runtime_config.permissions.allow_destructive_shell_commands or args.unsafe,
|
|
),
|
|
)
|
|
if args.stream:
|
|
runtime_config = replace(runtime_config, stream_model_responses=True)
|
|
if (
|
|
args.auto_snip_threshold is not None
|
|
or args.auto_compact_threshold is not None
|
|
or args.compact_preserve_messages is not None
|
|
):
|
|
runtime_config = replace(
|
|
runtime_config,
|
|
auto_snip_threshold_tokens=(
|
|
args.auto_snip_threshold
|
|
if args.auto_snip_threshold is not None
|
|
else runtime_config.auto_snip_threshold_tokens
|
|
),
|
|
auto_compact_threshold_tokens=(
|
|
args.auto_compact_threshold
|
|
if args.auto_compact_threshold is not None
|
|
else runtime_config.auto_compact_threshold_tokens
|
|
),
|
|
compact_preserve_messages=(
|
|
max(0, args.compact_preserve_messages)
|
|
if args.compact_preserve_messages is not None
|
|
else runtime_config.compact_preserve_messages
|
|
),
|
|
)
|
|
if (
|
|
args.max_total_tokens is not None
|
|
or args.max_input_tokens is not None
|
|
or args.max_output_tokens is not None
|
|
or args.max_reasoning_tokens is not None
|
|
or args.max_budget_usd is not None
|
|
or args.max_tool_calls is not None
|
|
or args.max_delegated_tasks is not None
|
|
or args.max_model_calls is not None
|
|
or args.max_session_turns is not None
|
|
):
|
|
runtime_config = replace(
|
|
runtime_config,
|
|
budget_config=BudgetConfig(
|
|
max_total_tokens=(
|
|
args.max_total_tokens
|
|
if args.max_total_tokens is not None
|
|
else runtime_config.budget_config.max_total_tokens
|
|
),
|
|
max_input_tokens=(
|
|
args.max_input_tokens
|
|
if args.max_input_tokens is not None
|
|
else runtime_config.budget_config.max_input_tokens
|
|
),
|
|
max_output_tokens=(
|
|
args.max_output_tokens
|
|
if args.max_output_tokens is not None
|
|
else runtime_config.budget_config.max_output_tokens
|
|
),
|
|
max_reasoning_tokens=(
|
|
args.max_reasoning_tokens
|
|
if args.max_reasoning_tokens is not None
|
|
else runtime_config.budget_config.max_reasoning_tokens
|
|
),
|
|
max_total_cost_usd=(
|
|
args.max_budget_usd
|
|
if args.max_budget_usd is not None
|
|
else runtime_config.budget_config.max_total_cost_usd
|
|
),
|
|
max_tool_calls=(
|
|
args.max_tool_calls
|
|
if args.max_tool_calls is not None
|
|
else runtime_config.budget_config.max_tool_calls
|
|
),
|
|
max_delegated_tasks=(
|
|
args.max_delegated_tasks
|
|
if args.max_delegated_tasks is not None
|
|
else runtime_config.budget_config.max_delegated_tasks
|
|
),
|
|
max_model_calls=(
|
|
args.max_model_calls
|
|
if args.max_model_calls is not None
|
|
else runtime_config.budget_config.max_model_calls
|
|
),
|
|
max_session_turns=(
|
|
args.max_session_turns
|
|
if args.max_session_turns is not None
|
|
else runtime_config.budget_config.max_session_turns
|
|
),
|
|
),
|
|
)
|
|
output_schema = _load_output_schema_config(args)
|
|
if output_schema is not None:
|
|
runtime_config = replace(runtime_config, output_schema=output_schema)
|
|
if args.scratchpad_root:
|
|
runtime_config = replace(
|
|
runtime_config,
|
|
scratchpad_root=Path(args.scratchpad_root).resolve(),
|
|
)
|
|
|
|
agent = LocalCodingAgent(
|
|
model_config=model_config,
|
|
runtime_config=runtime_config,
|
|
)
|
|
return agent, stored_session
|
|
|
|
|
|
def _print_agent_result(result, *, show_transcript: bool) -> None:
|
|
print(result.final_output)
|
|
print('\n# Usage')
|
|
print(f'total_tokens={result.usage.total_tokens}')
|
|
print(f'input_tokens={result.usage.input_tokens}')
|
|
print(f'output_tokens={result.usage.output_tokens}')
|
|
print(f'total_cost_usd={result.total_cost_usd:.6f}')
|
|
if result.stop_reason:
|
|
print(f'stop_reason={result.stop_reason}')
|
|
if result.session_id:
|
|
print('\n# Session')
|
|
print(f'session_id={result.session_id}')
|
|
if result.session_path:
|
|
print(f'session_path={result.session_path}')
|
|
if result.scratchpad_directory:
|
|
print(f'scratchpad_directory={result.scratchpad_directory}')
|
|
if show_transcript:
|
|
print('\n# Transcript')
|
|
for message in result.transcript:
|
|
role = message.get('role', 'unknown')
|
|
print(f'[{role}]')
|
|
print(message.get('content', ''))
|
|
|
|
|
|
def _run_agent_chat_loop(
|
|
agent: LocalCodingAgent,
|
|
*,
|
|
initial_prompt: str | None,
|
|
resume_session_id: str | None,
|
|
show_transcript: bool,
|
|
input_func: Callable[[str], str] = input,
|
|
output_func: Callable[[str], None] = print,
|
|
result_printer: Callable[..., None] = _print_agent_result,
|
|
) -> int:
|
|
active_session_id = resume_session_id
|
|
first_prompt = initial_prompt
|
|
|
|
output_func('# Agent Chat')
|
|
output_func("Enter a prompt. Use '/exit' or '/quit' to stop.")
|
|
if active_session_id:
|
|
output_func(f'resuming_session_id={active_session_id}')
|
|
|
|
while True:
|
|
if first_prompt is not None:
|
|
prompt = first_prompt
|
|
first_prompt = None
|
|
else:
|
|
try:
|
|
prompt = input_func('user> ')
|
|
except EOFError:
|
|
output_func('chat_ended=eof')
|
|
return 0
|
|
except KeyboardInterrupt:
|
|
output_func('\nchat_ended=interrupt')
|
|
return 130
|
|
|
|
normalized = prompt.strip()
|
|
if not normalized:
|
|
continue
|
|
if normalized in {'/exit', '/quit'}:
|
|
output_func('chat_ended=user_exit')
|
|
return 0
|
|
|
|
if active_session_id:
|
|
stored_session = load_agent_session(
|
|
active_session_id,
|
|
directory=agent.runtime_config.session_directory,
|
|
)
|
|
result = agent.resume(prompt, stored_session)
|
|
else:
|
|
result = agent.run(prompt)
|
|
result_printer(result, show_transcript=show_transcript)
|
|
active_session_id = result.session_id
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(description='Python porting workspace for the Claude Code rewrite effort')
|
|
subparsers = parser.add_subparsers(dest='command', required=True)
|
|
subparsers.add_parser('summary', help='render a Markdown summary of the Python porting workspace')
|
|
subparsers.add_parser('manifest', help='print the current Python workspace manifest')
|
|
subparsers.add_parser('parity-audit', help='compare the Python workspace against the local ignored TypeScript archive when available')
|
|
subparsers.add_parser('setup-report', help='render the startup/prefetch setup report')
|
|
subparsers.add_parser('command-graph', help='show command graph segmentation')
|
|
subparsers.add_parser('tool-pool', help='show assembled tool pool with default settings')
|
|
subparsers.add_parser('bootstrap-graph', help='show the mirrored bootstrap/runtime graph stages')
|
|
|
|
list_parser = subparsers.add_parser('subsystems', help='list the current Python modules in the workspace')
|
|
list_parser.add_argument('--limit', type=int, default=32)
|
|
|
|
commands_parser = subparsers.add_parser('commands', help='list mirrored command entries from the archived snapshot')
|
|
commands_parser.add_argument('--limit', type=int, default=20)
|
|
commands_parser.add_argument('--query')
|
|
commands_parser.add_argument('--no-plugin-commands', action='store_true')
|
|
commands_parser.add_argument('--no-skill-commands', action='store_true')
|
|
|
|
tools_parser = subparsers.add_parser('tools', help='list mirrored tool entries from the archived snapshot')
|
|
tools_parser.add_argument('--limit', type=int, default=20)
|
|
tools_parser.add_argument('--query')
|
|
tools_parser.add_argument('--simple-mode', action='store_true')
|
|
tools_parser.add_argument('--no-mcp', action='store_true')
|
|
tools_parser.add_argument('--deny-tool', action='append', default=[])
|
|
tools_parser.add_argument('--deny-prefix', action='append', default=[])
|
|
|
|
route_parser = subparsers.add_parser('route', help='route a prompt across mirrored command/tool inventories')
|
|
route_parser.add_argument('prompt')
|
|
route_parser.add_argument('--limit', type=int, default=5)
|
|
|
|
bootstrap_parser = subparsers.add_parser('bootstrap', help='build a runtime-style session report from the mirrored inventories')
|
|
bootstrap_parser.add_argument('prompt')
|
|
bootstrap_parser.add_argument('--limit', type=int, default=5)
|
|
|
|
loop_parser = subparsers.add_parser('turn-loop', help='run a small stateful turn loop for the mirrored runtime')
|
|
loop_parser.add_argument('prompt')
|
|
loop_parser.add_argument('--limit', type=int, default=5)
|
|
loop_parser.add_argument('--max-turns', type=int, default=3)
|
|
loop_parser.add_argument('--structured-output', action='store_true')
|
|
|
|
flush_parser = subparsers.add_parser('flush-transcript', help='persist and flush a temporary session transcript')
|
|
flush_parser.add_argument('prompt')
|
|
|
|
load_session_parser = subparsers.add_parser('load-session', help='load a previously persisted session')
|
|
load_session_parser.add_argument('session_id')
|
|
|
|
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='.')
|
|
worktree_status_parser = subparsers.add_parser('worktree-status', help='show local managed git worktree status')
|
|
worktree_status_parser.add_argument('--cwd', default='.')
|
|
worktree_enter_parser = subparsers.add_parser('worktree-enter', help='create and enter a managed git worktree')
|
|
worktree_enter_parser.add_argument('name', nargs='?')
|
|
worktree_enter_parser.add_argument('--cwd', default='.')
|
|
worktree_exit_parser = subparsers.add_parser('worktree-exit', help='exit the active managed git worktree')
|
|
worktree_exit_parser.add_argument('--action', default='keep')
|
|
worktree_exit_parser.add_argument('--discard-changes', action='store_true')
|
|
worktree_exit_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='.')
|
|
ask_status_parser = subparsers.add_parser('ask-status', help='show local ask-user runtime status')
|
|
ask_status_parser.add_argument('--cwd', default='.')
|
|
ask_history_parser = subparsers.add_parser('ask-history', help='show local ask-user interaction history')
|
|
ask_history_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='.')
|
|
lsp_status_parser = subparsers.add_parser('lsp-status', help='show local LSP runtime summary')
|
|
lsp_status_parser.add_argument('--cwd', default='.')
|
|
lsp_symbols_parser = subparsers.add_parser('lsp-symbols', help='show local LSP document symbols for one file')
|
|
lsp_symbols_parser.add_argument('file_path')
|
|
lsp_symbols_parser.add_argument('--cwd', default='.')
|
|
lsp_workspace_parser = subparsers.add_parser('lsp-workspace-symbols', help='search workspace symbols through the local LSP runtime')
|
|
lsp_workspace_parser.add_argument('query')
|
|
lsp_workspace_parser.add_argument('--max-results', type=int, default=50)
|
|
lsp_workspace_parser.add_argument('--cwd', default='.')
|
|
lsp_definition_parser = subparsers.add_parser('lsp-definition', help='run a local LSP definition query')
|
|
lsp_definition_parser.add_argument('file_path')
|
|
lsp_definition_parser.add_argument('line', type=int)
|
|
lsp_definition_parser.add_argument('character', type=int)
|
|
lsp_definition_parser.add_argument('--max-results', type=int, default=20)
|
|
lsp_definition_parser.add_argument('--cwd', default='.')
|
|
lsp_references_parser = subparsers.add_parser('lsp-references', help='run a local LSP references query')
|
|
lsp_references_parser.add_argument('file_path')
|
|
lsp_references_parser.add_argument('line', type=int)
|
|
lsp_references_parser.add_argument('character', type=int)
|
|
lsp_references_parser.add_argument('--max-results', type=int, default=50)
|
|
lsp_references_parser.add_argument('--cwd', default='.')
|
|
lsp_hover_parser = subparsers.add_parser('lsp-hover', help='run a local LSP hover query')
|
|
lsp_hover_parser.add_argument('file_path')
|
|
lsp_hover_parser.add_argument('line', type=int)
|
|
lsp_hover_parser.add_argument('character', type=int)
|
|
lsp_hover_parser.add_argument('--cwd', default='.')
|
|
lsp_diagnostics_parser = subparsers.add_parser('lsp-diagnostics', help='show local LSP diagnostics')
|
|
lsp_diagnostics_parser.add_argument('--file-path')
|
|
lsp_diagnostics_parser.add_argument('--cwd', default='.')
|
|
lsp_hierarchy_parser = subparsers.add_parser('lsp-call-hierarchy', help='show local LSP call hierarchy at a position')
|
|
lsp_hierarchy_parser.add_argument('file_path')
|
|
lsp_hierarchy_parser.add_argument('line', type=int)
|
|
lsp_hierarchy_parser.add_argument('character', type=int)
|
|
lsp_hierarchy_parser.add_argument('--cwd', default='.')
|
|
lsp_incoming_parser = subparsers.add_parser('lsp-incoming-calls', help='show local LSP incoming calls at a position')
|
|
lsp_incoming_parser.add_argument('file_path')
|
|
lsp_incoming_parser.add_argument('line', type=int)
|
|
lsp_incoming_parser.add_argument('character', type=int)
|
|
lsp_incoming_parser.add_argument('--max-results', type=int, default=50)
|
|
lsp_incoming_parser.add_argument('--cwd', default='.')
|
|
lsp_outgoing_parser = subparsers.add_parser('lsp-outgoing-calls', help='show local LSP outgoing calls at a position')
|
|
lsp_outgoing_parser.add_argument('file_path')
|
|
lsp_outgoing_parser.add_argument('line', type=int)
|
|
lsp_outgoing_parser.add_argument('character', type=int)
|
|
lsp_outgoing_parser.add_argument('--max-results', type=int, default=50)
|
|
lsp_outgoing_parser.add_argument('--cwd', default='.')
|
|
workflow_list_parser = subparsers.add_parser('workflow-list', help='list local workflow definitions')
|
|
workflow_list_parser.add_argument('--cwd', default='.')
|
|
workflow_list_parser.add_argument('--query')
|
|
workflow_get_parser = subparsers.add_parser('workflow-get', help='show one local workflow definition')
|
|
workflow_get_parser.add_argument('workflow_name')
|
|
workflow_get_parser.add_argument('--cwd', default='.')
|
|
workflow_run_parser = subparsers.add_parser('workflow-run', help='record and render a local workflow run')
|
|
workflow_run_parser.add_argument('workflow_name')
|
|
workflow_run_parser.add_argument('--arguments-json', default='{}')
|
|
workflow_run_parser.add_argument('--cwd', default='.')
|
|
trigger_list_parser = subparsers.add_parser('trigger-list', help='list local remote triggers')
|
|
trigger_list_parser.add_argument('--cwd', default='.')
|
|
trigger_list_parser.add_argument('--query')
|
|
trigger_get_parser = subparsers.add_parser('trigger-get', help='show one local remote trigger')
|
|
trigger_get_parser.add_argument('trigger_id')
|
|
trigger_get_parser.add_argument('--cwd', default='.')
|
|
trigger_create_parser = subparsers.add_parser('trigger-create', help='create a local remote trigger')
|
|
trigger_create_parser.add_argument('--body-json', required=True)
|
|
trigger_create_parser.add_argument('--cwd', default='.')
|
|
trigger_update_parser = subparsers.add_parser('trigger-update', help='update a local remote trigger')
|
|
trigger_update_parser.add_argument('trigger_id')
|
|
trigger_update_parser.add_argument('--body-json', required=True)
|
|
trigger_update_parser.add_argument('--cwd', default='.')
|
|
trigger_run_parser = subparsers.add_parser('trigger-run', help='run a local remote trigger')
|
|
trigger_run_parser.add_argument('trigger_id')
|
|
trigger_run_parser.add_argument('--body-json', default='{}')
|
|
trigger_run_parser.add_argument('--cwd', default='.')
|
|
teams_status_parser = subparsers.add_parser('team-status', help='show local collaboration team runtime summary')
|
|
teams_status_parser.add_argument('--cwd', default='.')
|
|
teams_list_parser = subparsers.add_parser('team-list', help='list local collaboration teams')
|
|
teams_list_parser.add_argument('--cwd', default='.')
|
|
teams_list_parser.add_argument('--query')
|
|
team_get_parser = subparsers.add_parser('team-get', help='show one local collaboration team')
|
|
team_get_parser.add_argument('team_name')
|
|
team_get_parser.add_argument('--cwd', default='.')
|
|
team_create_parser = subparsers.add_parser('team-create', help='create a local collaboration team')
|
|
team_create_parser.add_argument('team_name')
|
|
team_create_parser.add_argument('--description')
|
|
team_create_parser.add_argument('--member', action='append', default=[])
|
|
team_create_parser.add_argument('--cwd', default='.')
|
|
team_delete_parser = subparsers.add_parser('team-delete', help='delete a local collaboration team')
|
|
team_delete_parser.add_argument('team_name')
|
|
team_delete_parser.add_argument('--cwd', default='.')
|
|
team_messages_parser = subparsers.add_parser('team-messages', help='show local team messages')
|
|
team_messages_parser.add_argument('--team-name')
|
|
team_messages_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')
|
|
show_tool = subparsers.add_parser('show-tool', help='show one mirrored tool entry by exact name')
|
|
show_tool.add_argument('name')
|
|
|
|
exec_command_parser = subparsers.add_parser('exec-command', help='execute a mirrored command shim by exact name')
|
|
exec_command_parser.add_argument('name')
|
|
exec_command_parser.add_argument('prompt')
|
|
|
|
exec_tool_parser = subparsers.add_parser('exec-tool', help='execute a mirrored tool shim by exact name')
|
|
exec_tool_parser.add_argument('name')
|
|
exec_tool_parser.add_argument('payload')
|
|
|
|
agent_parser = subparsers.add_parser('agent', help='run the real Python local-model agent')
|
|
agent_parser.add_argument('prompt')
|
|
agent_parser.add_argument('--max-turns', type=int, default=12)
|
|
agent_parser.add_argument('--show-transcript', action='store_true')
|
|
_add_agent_common_args(agent_parser, include_backend=True)
|
|
|
|
background_parser = subparsers.add_parser('agent-bg', help='run the Python local-model agent as a local background session')
|
|
background_parser.add_argument('prompt')
|
|
background_parser.add_argument('--max-turns', type=int, default=12)
|
|
background_parser.add_argument('--show-transcript', action='store_true')
|
|
_add_agent_common_args(background_parser, include_backend=True)
|
|
|
|
background_worker_parser = subparsers.add_parser('agent-bg-worker', help=argparse.SUPPRESS)
|
|
background_worker_parser.add_argument('background_id')
|
|
background_worker_parser.add_argument('prompt')
|
|
background_worker_parser.add_argument('--background-root', required=True)
|
|
background_worker_parser.add_argument('--max-turns', type=int, default=12)
|
|
background_worker_parser.add_argument('--show-transcript', action='store_true')
|
|
_add_agent_common_args(background_worker_parser, include_backend=True)
|
|
|
|
ps_parser = subparsers.add_parser('agent-ps', help='list local background agent sessions')
|
|
ps_parser.add_argument('--tail', type=int, default=None)
|
|
|
|
logs_parser = subparsers.add_parser('agent-logs', help='show logs for a local background agent session')
|
|
logs_parser.add_argument('background_id')
|
|
logs_parser.add_argument('--tail', type=int, default=None)
|
|
|
|
attach_parser = subparsers.add_parser('agent-attach', help='show the current output snapshot for a local background agent session')
|
|
attach_parser.add_argument('background_id')
|
|
attach_parser.add_argument('--tail', type=int, default=None)
|
|
|
|
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')
|
|
chat_parser.add_argument('--max-turns', type=int, default=12)
|
|
chat_parser.add_argument('--show-transcript', action='store_true')
|
|
_add_agent_common_args(chat_parser, include_backend=True)
|
|
|
|
resume_parser = subparsers.add_parser('agent-resume', help='resume a saved Python local-model agent session')
|
|
_add_agent_resume_args(resume_parser)
|
|
|
|
prompt_parser = subparsers.add_parser('agent-prompt', help='render the Python agent system prompt')
|
|
_add_agent_common_args(prompt_parser, include_backend=False)
|
|
|
|
context_parser = subparsers.add_parser('agent-context', help='render Python /context-style usage accounting')
|
|
_add_agent_common_args(context_parser, include_backend=False)
|
|
|
|
context_raw_parser = subparsers.add_parser('agent-context-raw', help='render the raw Python agent context snapshot')
|
|
_add_agent_common_args(context_raw_parser, include_backend=False)
|
|
|
|
token_budget_parser = subparsers.add_parser('token-budget', help='render the current token budget and prompt-length limits')
|
|
_add_agent_common_args(token_budget_parser, include_backend=False)
|
|
return parser
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
manifest = build_port_manifest()
|
|
|
|
if args.command == 'summary':
|
|
print(QueryEnginePort(manifest).render_summary())
|
|
return 0
|
|
if args.command == 'manifest':
|
|
print(manifest.to_markdown())
|
|
return 0
|
|
if args.command == 'parity-audit':
|
|
print(run_parity_audit().to_markdown())
|
|
return 0
|
|
if args.command == 'setup-report':
|
|
print(run_setup().as_markdown())
|
|
return 0
|
|
if args.command == 'command-graph':
|
|
print(build_command_graph().as_markdown())
|
|
return 0
|
|
if args.command == 'tool-pool':
|
|
print(assemble_tool_pool().as_markdown())
|
|
return 0
|
|
if args.command == 'bootstrap-graph':
|
|
print(build_bootstrap_graph().as_markdown())
|
|
return 0
|
|
if args.command == 'subsystems':
|
|
for subsystem in manifest.top_level_modules[: args.limit]:
|
|
print(f'{subsystem.name}\t{subsystem.file_count}\t{subsystem.notes}')
|
|
return 0
|
|
if args.command == 'commands':
|
|
if args.query:
|
|
print(render_command_index(limit=args.limit, query=args.query))
|
|
else:
|
|
commands = get_commands(
|
|
include_plugin_commands=not args.no_plugin_commands,
|
|
include_skill_commands=not args.no_skill_commands,
|
|
)
|
|
output_lines = [f'Command entries: {len(commands)}', '']
|
|
output_lines.extend(f'- {module.name} — {module.source_hint}' for module in commands[: args.limit])
|
|
print('\n'.join(output_lines))
|
|
return 0
|
|
if args.command == 'tools':
|
|
if args.query:
|
|
print(render_tool_index(limit=args.limit, query=args.query))
|
|
else:
|
|
permission_context = ToolPermissionContext.from_iterables(args.deny_tool, args.deny_prefix)
|
|
tools = get_tools(
|
|
simple_mode=args.simple_mode,
|
|
include_mcp=not args.no_mcp,
|
|
permission_context=permission_context,
|
|
)
|
|
output_lines = [f'Tool entries: {len(tools)}', '']
|
|
output_lines.extend(f'- {module.name} — {module.source_hint}' for module in tools[: args.limit])
|
|
print('\n'.join(output_lines))
|
|
return 0
|
|
if args.command == 'route':
|
|
matches = PortRuntime().route_prompt(args.prompt, limit=args.limit)
|
|
if not matches:
|
|
print('No mirrored command/tool matches found.')
|
|
return 0
|
|
for match in matches:
|
|
print(f'{match.kind}\t{match.name}\t{match.score}\t{match.source_hint}')
|
|
return 0
|
|
if args.command == 'bootstrap':
|
|
print(PortRuntime().bootstrap_session(args.prompt, limit=args.limit).as_markdown())
|
|
return 0
|
|
if args.command == 'turn-loop':
|
|
results = PortRuntime().run_turn_loop(
|
|
args.prompt,
|
|
limit=args.limit,
|
|
max_turns=args.max_turns,
|
|
structured_output=args.structured_output,
|
|
)
|
|
for idx, result in enumerate(results, start=1):
|
|
print(f'## Turn {idx}')
|
|
print(result.output)
|
|
print(f'stop_reason={result.stop_reason}')
|
|
return 0
|
|
if args.command == 'flush-transcript':
|
|
engine = QueryEnginePort.from_workspace()
|
|
engine.submit_message(args.prompt)
|
|
path = engine.persist_session()
|
|
print(path)
|
|
print(f'flushed={engine.transcript_store.flushed}')
|
|
return 0
|
|
if args.command == 'load-session':
|
|
session = load_session(args.session_id)
|
|
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, cwd=Path(args.cwd).resolve()).as_text())
|
|
return 0
|
|
if args.command == 'ssh-mode':
|
|
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, cwd=Path(args.cwd).resolve()).as_text())
|
|
return 0
|
|
if args.command == 'direct-connect-mode':
|
|
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_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 == 'worktree-status':
|
|
runtime = WorktreeRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print('# Worktree')
|
|
print()
|
|
print(runtime.render_summary())
|
|
return 0
|
|
if args.command == 'worktree-enter':
|
|
runtime = WorktreeRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.enter(name=args.name).as_text())
|
|
except (RuntimeError, ValueError) as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'worktree-exit':
|
|
runtime = WorktreeRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(
|
|
runtime.exit(
|
|
action=args.action,
|
|
discard_changes=args.discard_changes,
|
|
).as_text()
|
|
)
|
|
except (RuntimeError, ValueError) as exc:
|
|
print(exc)
|
|
return 1
|
|
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 == 'ask-status':
|
|
runtime = AskUserRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print('# Ask User')
|
|
print()
|
|
print(runtime.render_summary())
|
|
return 0
|
|
if args.command == 'ask-history':
|
|
runtime = AskUserRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print(runtime.render_history())
|
|
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 == 'lsp-status':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print('# LSP')
|
|
print()
|
|
print(runtime.render_summary())
|
|
return 0
|
|
if args.command == 'lsp-symbols':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.render_document_symbols(args.file_path))
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'lsp-workspace-symbols':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print(runtime.render_workspace_symbols(args.query, max_results=args.max_results))
|
|
return 0
|
|
if args.command == 'lsp-definition':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(
|
|
runtime.render_definition(
|
|
args.file_path,
|
|
args.line,
|
|
args.character,
|
|
max_results=args.max_results,
|
|
)
|
|
)
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'lsp-references':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(
|
|
runtime.render_references(
|
|
args.file_path,
|
|
args.line,
|
|
args.character,
|
|
max_results=args.max_results,
|
|
)
|
|
)
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'lsp-hover':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.render_hover(args.file_path, args.line, args.character))
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'lsp-diagnostics':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.render_diagnostics(args.file_path))
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'lsp-call-hierarchy':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(
|
|
runtime.render_prepare_call_hierarchy(
|
|
args.file_path,
|
|
args.line,
|
|
args.character,
|
|
)
|
|
)
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'lsp-incoming-calls':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(
|
|
runtime.render_incoming_calls(
|
|
args.file_path,
|
|
args.line,
|
|
args.character,
|
|
max_results=args.max_results,
|
|
)
|
|
)
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'lsp-outgoing-calls':
|
|
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(
|
|
runtime.render_outgoing_calls(
|
|
args.file_path,
|
|
args.line,
|
|
args.character,
|
|
max_results=args.max_results,
|
|
)
|
|
)
|
|
except KeyError as exc:
|
|
print(exc)
|
|
return 1
|
|
return 0
|
|
if args.command == 'workflow-list':
|
|
runtime = WorkflowRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print(runtime.render_workflows_index(query=args.query))
|
|
return 0
|
|
if args.command == 'workflow-get':
|
|
runtime = WorkflowRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.render_workflow(args.workflow_name))
|
|
except KeyError:
|
|
print(f'Unknown workflow: {args.workflow_name}')
|
|
return 1
|
|
return 0
|
|
if args.command == 'workflow-run':
|
|
runtime = WorkflowRuntime.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
|
|
try:
|
|
print(runtime.render_run_report(args.workflow_name, arguments=arguments))
|
|
except KeyError:
|
|
print(f'Unknown workflow: {args.workflow_name}')
|
|
return 1
|
|
return 0
|
|
if args.command == 'trigger-list':
|
|
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print(runtime.render_trigger_index(query=args.query))
|
|
return 0
|
|
if args.command == 'trigger-get':
|
|
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.render_trigger(args.trigger_id))
|
|
except KeyError:
|
|
print(f'Unknown remote trigger: {args.trigger_id}')
|
|
return 1
|
|
return 0
|
|
if args.command == 'trigger-create':
|
|
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
|
|
body = json.loads(args.body_json)
|
|
if not isinstance(body, dict):
|
|
print('body-json must decode to a JSON object')
|
|
return 1
|
|
try:
|
|
trigger = runtime.create_trigger(body)
|
|
except (KeyError, TypeError, ValueError) as exc:
|
|
print(exc)
|
|
return 1
|
|
print(runtime.render_trigger(trigger.trigger_id))
|
|
return 0
|
|
if args.command == 'trigger-update':
|
|
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
|
|
body = json.loads(args.body_json)
|
|
if not isinstance(body, dict):
|
|
print('body-json must decode to a JSON object')
|
|
return 1
|
|
try:
|
|
trigger = runtime.update_trigger(args.trigger_id, body)
|
|
except (KeyError, TypeError, ValueError) as exc:
|
|
print(exc)
|
|
return 1
|
|
print(runtime.render_trigger(trigger.trigger_id))
|
|
return 0
|
|
if args.command == 'trigger-run':
|
|
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
|
|
body = json.loads(args.body_json)
|
|
if not isinstance(body, dict):
|
|
print('body-json must decode to a JSON object')
|
|
return 1
|
|
try:
|
|
print(runtime.render_run_report(args.trigger_id, body=body))
|
|
except KeyError:
|
|
print(f'Unknown remote trigger: {args.trigger_id}')
|
|
return 1
|
|
return 0
|
|
if args.command == 'team-status':
|
|
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print('# Teams')
|
|
print()
|
|
print(runtime.render_summary())
|
|
return 0
|
|
if args.command == 'team-list':
|
|
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
|
|
print(runtime.render_teams_index(query=args.query))
|
|
return 0
|
|
if args.command == 'team-get':
|
|
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.render_team(args.team_name))
|
|
except KeyError:
|
|
print(f'Unknown team: {args.team_name}')
|
|
return 1
|
|
return 0
|
|
if args.command == 'team-create':
|
|
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
team = runtime.create_team(
|
|
args.team_name,
|
|
description=args.description,
|
|
members=args.member,
|
|
)
|
|
except KeyError:
|
|
print(f'Team already exists: {args.team_name}')
|
|
return 1
|
|
print(f'created team {team.name}')
|
|
return 0
|
|
if args.command == 'team-delete':
|
|
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
team = runtime.delete_team(args.team_name)
|
|
except KeyError:
|
|
print(f'Unknown team: {args.team_name}')
|
|
return 1
|
|
print(f'deleted team {team.name}')
|
|
return 0
|
|
if args.command == 'team-messages':
|
|
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
|
|
try:
|
|
print(runtime.render_messages(team_name=args.team_name))
|
|
except KeyError:
|
|
print(f'Unknown team: {args.team_name}')
|
|
return 1
|
|
return 0
|
|
if args.command == 'show-command':
|
|
module = get_command(args.name)
|
|
if module is None:
|
|
print(f'Command not found: {args.name}')
|
|
return 1
|
|
print('\n'.join([module.name, module.source_hint, module.responsibility]))
|
|
return 0
|
|
if args.command == 'show-tool':
|
|
module = get_tool(args.name)
|
|
if module is None:
|
|
print(f'Tool not found: {args.name}')
|
|
return 1
|
|
print('\n'.join([module.name, module.source_hint, module.responsibility]))
|
|
return 0
|
|
if args.command == 'exec-command':
|
|
result = execute_command(args.name, args.prompt)
|
|
print(result.message)
|
|
return 0 if result.handled else 1
|
|
if args.command == 'exec-tool':
|
|
result = execute_tool(args.name, args.payload)
|
|
print(result.message)
|
|
return 0 if result.handled else 1
|
|
if args.command == 'agent':
|
|
agent = _build_agent(args)
|
|
result = agent.run(args.prompt)
|
|
_print_agent_result(result, show_transcript=args.show_transcript)
|
|
return 0
|
|
if args.command == 'agent-bg':
|
|
return _launch_background_agent(args)
|
|
if args.command == 'agent-bg-worker':
|
|
return _run_background_worker(args)
|
|
if args.command == 'agent-ps':
|
|
print(BackgroundSessionRuntime().render_ps())
|
|
return 0
|
|
if args.command == 'agent-logs':
|
|
print(
|
|
BackgroundSessionRuntime().render_logs(
|
|
args.background_id,
|
|
tail=args.tail,
|
|
)
|
|
)
|
|
return 0
|
|
if args.command == 'agent-attach':
|
|
print(
|
|
BackgroundSessionRuntime().render_attach(
|
|
args.background_id,
|
|
tail=args.tail,
|
|
)
|
|
)
|
|
return 0
|
|
if args.command == 'agent-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 == '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(
|
|
agent,
|
|
initial_prompt=args.prompt,
|
|
resume_session_id=args.resume_session_id,
|
|
show_transcript=args.show_transcript,
|
|
)
|
|
if args.command == 'agent-resume':
|
|
agent, stored_session = _build_resumed_agent(args)
|
|
result = agent.resume(args.prompt, stored_session)
|
|
_print_agent_result(result, show_transcript=args.show_transcript)
|
|
return 0
|
|
if args.command == 'agent-prompt':
|
|
agent = _build_agent(args)
|
|
print(agent.render_system_prompt())
|
|
return 0
|
|
if args.command == 'agent-context':
|
|
agent = _build_agent(args)
|
|
print(agent.render_context_report())
|
|
return 0
|
|
if args.command == 'agent-context-raw':
|
|
agent = _build_agent(args)
|
|
print(agent.render_context_snapshot_report())
|
|
return 0
|
|
if args.command == 'token-budget':
|
|
agent = _build_agent(args)
|
|
agent.last_session = agent.build_session()
|
|
print(agent.render_token_budget_report())
|
|
return 0
|
|
|
|
parser.error(f'unknown command: {args.command}')
|
|
return 2
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|