alysis-code 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alysis_code/__init__.py +3 -0
- alysis_code/__main__.py +4 -0
- alysis_code/_build_info.py +14 -0
- alysis_code/account_login.py +468 -0
- alysis_code/agent/README.md +35 -0
- alysis_code/agent/__init__.py +11 -0
- alysis_code/agent/acceptance_contract.py +2217 -0
- alysis_code/agent/blast_radius.py +1403 -0
- alysis_code/agent/cache_keepalive.py +227 -0
- alysis_code/agent/completion_certificate.py +366 -0
- alysis_code/agent/completion_gate.py +306 -0
- alysis_code/agent/empty_response_stall.py +403 -0
- alysis_code/agent/errors.py +28 -0
- alysis_code/agent/llm_calls.py +475 -0
- alysis_code/agent/mutation_classification.py +227 -0
- alysis_code/agent/prompt_context.py +2508 -0
- alysis_code/agent/read_ledger.py +253 -0
- alysis_code/agent/regression_baseline.py +642 -0
- alysis_code/agent/reproduction_first.py +610 -0
- alysis_code/agent/sensitive_output.py +629 -0
- alysis_code/agent/session.py +3218 -0
- alysis_code/agent/steering.py +191 -0
- alysis_code/agent/subagent_execution.py +5177 -0
- alysis_code/agent/subagent_workspace.py +666 -0
- alysis_code/agent/tools_assembly.py +4728 -0
- alysis_code/agent/turn/__init__.py +101 -0
- alysis_code/agent/turn/core.py +8483 -0
- alysis_code/agent/turn/events.py +113 -0
- alysis_code/agent/turn/exploration.py +590 -0
- alysis_code/agent/turn/interventions.py +65 -0
- alysis_code/agent/turn/read_cache.py +420 -0
- alysis_code/agent/turn/snapshot.py +179 -0
- alysis_code/agent/turn_contract.py +661 -0
- alysis_code/agent/turn_path.py +129 -0
- alysis_code/agent/verification.py +2885 -0
- alysis_code/agent/verification_commands.py +512 -0
- alysis_code/agent/verification_evidence.py +738 -0
- alysis_code/agent_loop.py +693 -0
- alysis_code/agent_runtimes/__init__.py +51 -0
- alysis_code/agent_runtimes/base.py +114 -0
- alysis_code/agent_runtimes/builtins.py +129 -0
- alysis_code/agent_runtimes/codex_cli.py +664 -0
- alysis_code/agent_runtimes/host.py +263 -0
- alysis_code/agent_runtimes/registry.py +64 -0
- alysis_code/agent_runtimes/service.py +150 -0
- alysis_code/agentbox_client.py +416 -0
- alysis_code/agentbox_integration.py +310 -0
- alysis_code/alysis_cloud.py +152 -0
- alysis_code/approval_scope.py +276 -0
- alysis_code/assets/README.md +33 -0
- alysis_code/assets/__init__.py +126 -0
- alysis_code/assets/asset_read_core.py +281 -0
- alysis_code/assets/budget_allocator.py +456 -0
- alysis_code/assets/comprehender.py +759 -0
- alysis_code/assets/index.py +654 -0
- alysis_code/assets/ingestion.py +275 -0
- alysis_code/assets/legacy_migration.py +413 -0
- alysis_code/assets/models.py +263 -0
- alysis_code/assets/ocr.py +239 -0
- alysis_code/assets/owl/ascii/f-000.txt +13 -0
- alysis_code/assets/owl/ascii/f-001.txt +13 -0
- alysis_code/assets/owl/ascii/f-002.txt +13 -0
- alysis_code/assets/owl/ascii/f-003.txt +13 -0
- alysis_code/assets/owl/ascii/f-004.txt +13 -0
- alysis_code/assets/owl/ascii/f-005.txt +13 -0
- alysis_code/assets/owl/ascii/f-006.txt +13 -0
- alysis_code/assets/owl/ascii/f-007.txt +13 -0
- alysis_code/assets/owl/ascii/f-008.txt +13 -0
- alysis_code/assets/owl/ascii/f-009.txt +13 -0
- alysis_code/assets/owl/ascii/f-010.txt +13 -0
- alysis_code/assets/owl/ascii/f-011.txt +13 -0
- alysis_code/assets/owl/ascii/f-012.txt +13 -0
- alysis_code/assets/owl/ascii/f-013.txt +13 -0
- alysis_code/assets/owl/ascii/f-014.txt +13 -0
- alysis_code/assets/owl/ascii/f-015.txt +13 -0
- alysis_code/assets/owl/ascii/f-016.txt +13 -0
- alysis_code/assets/owl/ascii/f-017.txt +13 -0
- alysis_code/assets/owl/ascii/f-018.txt +13 -0
- alysis_code/assets/owl/ascii/f-019.txt +13 -0
- alysis_code/assets/owl/ascii/f-020.txt +13 -0
- alysis_code/assets/owl/index.html +98 -0
- alysis_code/assets/owl/show-owl.sh +761 -0
- alysis_code/assets/paths.py +49 -0
- alysis_code/assets/plan_binding.py +326 -0
- alysis_code/assets/planner_context.py +466 -0
- alysis_code/assets/planner_tools.py +184 -0
- alysis_code/assets/prompts.py +101 -0
- alysis_code/assets/replanner_context.py +239 -0
- alysis_code/assets/surface.py +521 -0
- alysis_code/assets/untrusted_content.py +48 -0
- alysis_code/assets/usage_logger.py +94 -0
- alysis_code/assets/worker_mirror.py +428 -0
- alysis_code/assets/worker_section.py +303 -0
- alysis_code/assets/worker_tools.py +468 -0
- alysis_code/atomic_io.py +83 -0
- alysis_code/auth_diagnostics.py +272 -0
- alysis_code/background_runner.py +366 -0
- alysis_code/branding.py +270 -0
- alysis_code/budget_policy.py +390 -0
- alysis_code/build_identity.py +465 -0
- alysis_code/builtin_hooks/__init__.py +7 -0
- alysis_code/builtin_hooks/notify_done_windows.py +65 -0
- alysis_code/bwrap_etc.py +76 -0
- alysis_code/cancellation.py +41 -0
- alysis_code/capabilities.py +137 -0
- alysis_code/chatgpt_codex_static_provider.py +133 -0
- alysis_code/cli.py +51 -0
- alysis_code/cli_impl/__init__.py +1 -0
- alysis_code/cli_impl/assets_cli.py +537 -0
- alysis_code/cli_impl/assets_modal.py +412 -0
- alysis_code/cli_impl/chat/__init__.py +156 -0
- alysis_code/cli_impl/chat/commands.py +2616 -0
- alysis_code/cli_impl/chat/loop.py +4508 -0
- alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
- alysis_code/cli_impl/chat/rendering.py +444 -0
- alysis_code/cli_impl/chat/state.py +124 -0
- alysis_code/cli_impl/chat_resume.py +830 -0
- alysis_code/cli_impl/chat_slash_completer.py +258 -0
- alysis_code/cli_impl/commands/__init__.py +11 -0
- alysis_code/cli_impl/commands/_shared.py +89 -0
- alysis_code/cli_impl/commands/auth.py +623 -0
- alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
- alysis_code/cli_impl/commands/chat_state.py +158 -0
- alysis_code/cli_impl/commands/chat_status.py +1248 -0
- alysis_code/cli_impl/commands/chat_terminal.py +942 -0
- alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
- alysis_code/cli_impl/commands/cli_common.py +1223 -0
- alysis_code/cli_impl/commands/cli_surface.py +77 -0
- alysis_code/cli_impl/commands/config.py +131 -0
- alysis_code/cli_impl/commands/conventions.py +85 -0
- alysis_code/cli_impl/commands/execution_helpers.py +350 -0
- alysis_code/cli_impl/commands/extensions.py +401 -0
- alysis_code/cli_impl/commands/forge.py +1282 -0
- alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
- alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
- alysis_code/cli_impl/commands/hooks.py +737 -0
- alysis_code/cli_impl/commands/ide_bridge.py +31 -0
- alysis_code/cli_impl/commands/mcp.py +700 -0
- alysis_code/cli_impl/commands/profile.py +453 -0
- alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
- alysis_code/cli_impl/commands/report.py +88 -0
- alysis_code/cli_impl/commands/root.py +1118 -0
- alysis_code/cli_impl/commands/sandbox.py +184 -0
- alysis_code/cli_impl/commands/server.py +54 -0
- alysis_code/cli_impl/commands/sessions.py +252 -0
- alysis_code/cli_impl/commands/skills.py +404 -0
- alysis_code/cli_impl/commands/startup.py +946 -0
- alysis_code/cli_impl/commands/tools.py +335 -0
- alysis_code/cli_impl/commands/update.py +364 -0
- alysis_code/cli_impl/commands/welcome.py +972 -0
- alysis_code/cli_impl/config_menu.py +3882 -0
- alysis_code/cli_impl/forge.py +4509 -0
- alysis_code/cli_impl/forge_recovery.py +485 -0
- alysis_code/cli_impl/setup_wizard.py +2409 -0
- alysis_code/cli_impl/tui/__init__.py +58 -0
- alysis_code/cli_impl/tui/app.py +4551 -0
- alysis_code/cli_impl/tui/config.py +32 -0
- alysis_code/cli_impl/tui/config_flow.py +2754 -0
- alysis_code/cli_impl/tui/config_overlay.py +566 -0
- alysis_code/cli_impl/tui/content.py +78 -0
- alysis_code/cli_impl/tui/footer.py +218 -0
- alysis_code/cli_impl/tui/forge_status.py +136 -0
- alysis_code/cli_impl/tui/markdown.py +244 -0
- alysis_code/cli_impl/tui/owl.py +109 -0
- alysis_code/cli_impl/tui/plan_meta.py +477 -0
- alysis_code/cli_impl/tui/setup_app.py +519 -0
- alysis_code/cli_impl/tui/setup_flow.py +1622 -0
- alysis_code/cli_impl/tui/state.py +101 -0
- alysis_code/cli_impl/tui/subagent_identity.py +66 -0
- alysis_code/cli_impl/tui/subagent_panel.py +186 -0
- alysis_code/cli_impl/tui/surface.py +796 -0
- alysis_code/cli_impl/tui/transcript.py +514 -0
- alysis_code/cli_impl/tui/update_prompt.py +79 -0
- alysis_code/cli_impl/tui/workspace_guard.py +384 -0
- alysis_code/clipboard.py +172 -0
- alysis_code/code_review.py +1211 -0
- alysis_code/compaction/__init__.py +28 -0
- alysis_code/compaction/conversation_compactor.py +2932 -0
- alysis_code/compaction/importance.py +177 -0
- alysis_code/compaction/settings.py +297 -0
- alysis_code/compaction/tool_output_offload.py +447 -0
- alysis_code/config.py +3509 -0
- alysis_code/conflict_auto_resolver.py +895 -0
- alysis_code/context/__init__.py +1 -0
- alysis_code/context/tool_schema_budgeter.py +220 -0
- alysis_code/crash_diagnostics.py +282 -0
- alysis_code/custom_tools/README.md +34 -0
- alysis_code/custom_tools/__init__.py +43 -0
- alysis_code/custom_tools/discovery.py +903 -0
- alysis_code/custom_tools/runtime.py +1516 -0
- alysis_code/custom_tools/session.py +227 -0
- alysis_code/custom_tools/trust.py +232 -0
- alysis_code/diff_paths.py +113 -0
- alysis_code/direction_change.py +293 -0
- alysis_code/dispatch_timing.py +306 -0
- alysis_code/durable_service_manager.py +1236 -0
- alysis_code/edit_discipline.py +659 -0
- alysis_code/error_text.py +73 -0
- alysis_code/execution_budget.py +411 -0
- alysis_code/execution_context.py +915 -0
- alysis_code/execution_deadline.py +1065 -0
- alysis_code/execution_shared.py +1904 -0
- alysis_code/extensions/README.md +30 -0
- alysis_code/extensions/__init__.py +93 -0
- alysis_code/extensions/activation.py +138 -0
- alysis_code/extensions/install.py +1436 -0
- alysis_code/extensions/manifest.py +487 -0
- alysis_code/extensions/models.py +74 -0
- alysis_code/extensions/paths.py +56 -0
- alysis_code/extensions/registry.json +4 -0
- alysis_code/extensions/registry.py +52 -0
- alysis_code/extensions/state.py +83 -0
- alysis_code/extensions/workspace_trust.py +101 -0
- alysis_code/failed_task_evidence.py +369 -0
- alysis_code/failure_category.py +315 -0
- alysis_code/feedback_report.py +1647 -0
- alysis_code/file_classification.py +485 -0
- alysis_code/forge.py +2064 -0
- alysis_code/forge_completion.py +362 -0
- alysis_code/forge_events.py +475 -0
- alysis_code/frontmatter_utils.py +95 -0
- alysis_code/git_evidence.py +1181 -0
- alysis_code/git_ops.py +560 -0
- alysis_code/git_safe.py +62 -0
- alysis_code/git_worktrees.py +190 -0
- alysis_code/hooks/README.md +33 -0
- alysis_code/hooks/__init__.py +67 -0
- alysis_code/hooks/audit.py +171 -0
- alysis_code/hooks/config.py +225 -0
- alysis_code/hooks/dispatcher.py +1110 -0
- alysis_code/hooks/models.py +447 -0
- alysis_code/hooks/trust.py +202 -0
- alysis_code/host_actions.py +543 -0
- alysis_code/host_browser.py +103 -0
- alysis_code/ide/__init__.py +5 -0
- alysis_code/ide/activity_events.py +399 -0
- alysis_code/ide/approvals.py +337 -0
- alysis_code/ide/artifacts.py +153 -0
- alysis_code/ide/browser_egress_proxy.py +1076 -0
- alysis_code/ide/cdp_websocket_transport.py +1192 -0
- alysis_code/ide/change_ledger.py +1721 -0
- alysis_code/ide/context_blocks.py +979 -0
- alysis_code/ide/event_stream.py +531 -0
- alysis_code/ide/forge_protocol.py +3112 -0
- alysis_code/ide/forge_request_ledger.py +737 -0
- alysis_code/ide/health.py +965 -0
- alysis_code/ide/managed_browser.py +2251 -0
- alysis_code/ide/management_protocol.py +3414 -0
- alysis_code/ide/mcp_oauth_coordinator.py +744 -0
- alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
- alysis_code/ide/prompt_queue.py +1070 -0
- alysis_code/ide/protocol.py +191 -0
- alysis_code/ide/resumable_swarm.py +1543 -0
- alysis_code/ide/session_search.py +295 -0
- alysis_code/ide/stdio_bridge.py +9935 -0
- alysis_code/ide/structured_state.py +1579 -0
- alysis_code/ide/swarm_protocol.py +816 -0
- alysis_code/integration_gate.py +506 -0
- alysis_code/interactive_input_guard.py +39 -0
- alysis_code/interactive_plan_mode.py +26 -0
- alysis_code/internal_artifacts.py +179 -0
- alysis_code/knowledge_base.py +1409 -0
- alysis_code/knowledge_capture.py +1190 -0
- alysis_code/knowledge_librarian.py +605 -0
- alysis_code/language_policy.py +34 -0
- alysis_code/litellm_static_provider.py +535 -0
- alysis_code/llm/__init__.py +1 -0
- alysis_code/llm/anthropic_messages.py +2288 -0
- alysis_code/llm/base.py +71 -0
- alysis_code/llm/cache_capabilities.py +985 -0
- alysis_code/llm/cache_control_blocks.py +244 -0
- alysis_code/llm/cache_policy.py +388 -0
- alysis_code/llm/factory.py +373 -0
- alysis_code/llm/gemini_generate_content.py +2652 -0
- alysis_code/llm/gemini_interactions.py +739 -0
- alysis_code/llm/metadata.py +450 -0
- alysis_code/llm/openai_compat.py +2947 -0
- alysis_code/llm/openai_responses.py +2604 -0
- alysis_code/llm/protocols.py +609 -0
- alysis_code/llm/provider_limits.py +525 -0
- alysis_code/llm/request_plan.py +389 -0
- alysis_code/llm/request_shape.py +238 -0
- alysis_code/llm/streaming.py +108 -0
- alysis_code/llm/temperature_compat.py +78 -0
- alysis_code/llm/types.py +195 -0
- alysis_code/llm/usage_normalization.py +222 -0
- alysis_code/llm_error_display.py +315 -0
- alysis_code/logging_redaction.py +326 -0
- alysis_code/managed_host_deadline.py +196 -0
- alysis_code/mcp/README.md +33 -0
- alysis_code/mcp/__init__.py +24 -0
- alysis_code/mcp/client.py +1137 -0
- alysis_code/mcp/config.py +597 -0
- alysis_code/mcp/errors.py +113 -0
- alysis_code/mcp/forge_scope.py +154 -0
- alysis_code/mcp/jsonrpc.py +214 -0
- alysis_code/mcp/manager.py +2308 -0
- alysis_code/mcp/models.py +666 -0
- alysis_code/mcp/oauth.py +972 -0
- alysis_code/mcp/oauth_runtime.py +310 -0
- alysis_code/mcp/oauth_store.py +276 -0
- alysis_code/mcp/prompts.py +329 -0
- alysis_code/mcp/resources.py +295 -0
- alysis_code/mcp/roots.py +106 -0
- alysis_code/mcp/server_requests.py +75 -0
- alysis_code/mcp/token_store.py +859 -0
- alysis_code/mcp/transport_http.py +1338 -0
- alysis_code/mcp/transport_stdio.py +1267 -0
- alysis_code/mcp/untrusted_content.py +119 -0
- alysis_code/merge_conflict_reviewer.py +729 -0
- alysis_code/model_catalog/__init__.py +1 -0
- alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
- alysis_code/model_metadata_policy.py +223 -0
- alysis_code/model_metadata_utils.py +103 -0
- alysis_code/model_registry.py +1420 -0
- alysis_code/model_router.py +147 -0
- alysis_code/permission_policy.py +1016 -0
- alysis_code/personas.py +451 -0
- alysis_code/pipeline_facts.py +233 -0
- alysis_code/plan_assistant.py +4763 -0
- alysis_code/plan_mode.py +393 -0
- alysis_code/plan_reconciliation.py +1228 -0
- alysis_code/plan_repair.py +652 -0
- alysis_code/plan_validation.py +1099 -0
- alysis_code/planning_constraints.py +904 -0
- alysis_code/policy.py +95 -0
- alysis_code/preview_server.py +457 -0
- alysis_code/process_reaping.py +566 -0
- alysis_code/profile_presets.py +1834 -0
- alysis_code/profiles.py +666 -0
- alysis_code/provider_auth/__init__.py +29 -0
- alysis_code/provider_auth/base.py +99 -0
- alysis_code/provider_auth/openai_codex.py +951 -0
- alysis_code/provider_auth/registry.py +76 -0
- alysis_code/provider_auth/store.py +125 -0
- alysis_code/provider_diagnostics.py +1209 -0
- alysis_code/provider_model_catalog.py +685 -0
- alysis_code/provider_telemetry.py +1699 -0
- alysis_code/provider_url.py +75 -0
- alysis_code/reasoning_contracts.py +911 -0
- alysis_code/remote_sync.py +350 -0
- alysis_code/replanning.py +1195 -0
- alysis_code/repo_scan.py +1152 -0
- alysis_code/request_estimation.py +296 -0
- alysis_code/review_gate.py +617 -0
- alysis_code/run_lock.py +1141 -0
- alysis_code/run_outcome.py +58 -0
- alysis_code/run_provenance.py +774 -0
- alysis_code/run_state.py +445 -0
- alysis_code/runtime_artifacts.py +116 -0
- alysis_code/runtime_context_features.py +78 -0
- alysis_code/runtime_kind.py +52 -0
- alysis_code/safety/__init__.py +11 -0
- alysis_code/safety/mcp_sanitize.py +29 -0
- alysis_code/safety/safe_http.py +297 -0
- alysis_code/safety/subagent_report.py +184 -0
- alysis_code/sandbox_doctor.py +682 -0
- alysis_code/sandbox_runner.py +1025 -0
- alysis_code/sandbox_settings.py +423 -0
- alysis_code/serialized_paths.py +355 -0
- alysis_code/server/__init__.py +3 -0
- alysis_code/server/app.py +367 -0
- alysis_code/server/auth.py +34 -0
- alysis_code/server/job_config.py +30 -0
- alysis_code/server/settings.py +215 -0
- alysis_code/server/store.py +193 -0
- alysis_code/server/worker_runner.py +657 -0
- alysis_code/service_persistence.py +355 -0
- alysis_code/session_artifacts.py +108 -0
- alysis_code/session_metrics.py +331 -0
- alysis_code/session_store.py +624 -0
- alysis_code/skills/README.md +34 -0
- alysis_code/skills/__init__.py +104 -0
- alysis_code/skills/conventions.py +84 -0
- alysis_code/skills/discovery.py +176 -0
- alysis_code/skills/eval_models.py +232 -0
- alysis_code/skills/eval_runner.py +372 -0
- alysis_code/skills/evals.py +1344 -0
- alysis_code/skills/install.py +293 -0
- alysis_code/skills/loader.py +118 -0
- alysis_code/skills/matching.py +103 -0
- alysis_code/skills/models.py +71 -0
- alysis_code/skills/paths.py +56 -0
- alysis_code/skills/prompting.py +500 -0
- alysis_code/skills/scaffold.py +142 -0
- alysis_code/skills/state.py +441 -0
- alysis_code/skills/transactions.py +125 -0
- alysis_code/skills/validation.py +304 -0
- alysis_code/step_budget.py +238 -0
- alysis_code/subagent_labels.py +49 -0
- alysis_code/subagents.py +1072 -0
- alysis_code/surface/__init__.py +80 -0
- alysis_code/surface/base.py +305 -0
- alysis_code/surface/console.py +387 -0
- alysis_code/surface/events.py +372 -0
- alysis_code/surface/hidden_surface.py +529 -0
- alysis_code/surface/noop_surface.py +219 -0
- alysis_code/surface/rich_surface.py +1555 -0
- alysis_code/surface/styles.py +67 -0
- alysis_code/surface/theme.py +455 -0
- alysis_code/surface/types.py +100 -0
- alysis_code/swarm_backend.py +926 -0
- alysis_code/swarm_orchestrator.py +4020 -0
- alysis_code/swarm_scheduler.py +441 -0
- alysis_code/swarm_trace.py +429 -0
- alysis_code/swarm_worker.py +2119 -0
- alysis_code/swarm_write_guard.py +348 -0
- alysis_code/task_dependencies.py +170 -0
- alysis_code/task_readiness.py +992 -0
- alysis_code/task_scope.py +2148 -0
- alysis_code/terminal_manager.py +762 -0
- alysis_code/terminal_ownership.py +460 -0
- alysis_code/text_normalization.py +30 -0
- alysis_code/token_budget.py +97 -0
- alysis_code/tools/README.md +34 -0
- alysis_code/tools/__init__.py +1 -0
- alysis_code/tools/artifacts.py +127 -0
- alysis_code/tools/availability.py +188 -0
- alysis_code/tools/fs.py +1456 -0
- alysis_code/tools/git.py +461 -0
- alysis_code/tools/history.py +229 -0
- alysis_code/tools/http_timeout.py +78 -0
- alysis_code/tools/image_generation.py +552 -0
- alysis_code/tools/registry.py +2936 -0
- alysis_code/tools/repo_map.py +476 -0
- alysis_code/tools/search.py +563 -0
- alysis_code/tools/shell.py +135 -0
- alysis_code/tools/symbols.py +1350 -0
- alysis_code/tools/test_discovery.py +643 -0
- alysis_code/tools/web.py +482 -0
- alysis_code/tools/web_search.py +2012 -0
- alysis_code/tools/web_search_dashscope.py +557 -0
- alysis_code/tools/web_search_ddgs.py +221 -0
- alysis_code/tools/web_search_provider_adapters.py +1429 -0
- alysis_code/tools/web_search_tavily.py +194 -0
- alysis_code/updates.py +933 -0
- alysis_code/usage_tracker.py +1990 -0
- alysis_code/verification_command_analysis.py +1004 -0
- alysis_code/verification_contract.py +574 -0
- alysis_code/verification_failure_summary.py +273 -0
- alysis_code/verification_repair.py +385 -0
- alysis_code/verify_gate.py +3129 -0
- alysis_code/web_research.py +1872 -0
- alysis_code/web_search_adapters.py +66 -0
- alysis_code/web_search_policy.py +27 -0
- alysis_code/workspace_binding.py +389 -0
- alysis_code/workspace_binding_ui.py +408 -0
- alysis_code/workspace_context.py +273 -0
- alysis_code/workspace_isolation.py +138 -0
- alysis_code/workspace_provisioning.py +455 -0
- alysis_code-0.13.0.dist-info/METADATA +507 -0
- alysis_code-0.13.0.dist-info/RECORD +458 -0
- alysis_code-0.13.0.dist-info/WHEEL +4 -0
- alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
- alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
- alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,2936 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Callable, Collection
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from difflib import SequenceMatcher
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .availability import is_tool_unavailable_result
|
|
12
|
+
|
|
13
|
+
ToolFormatter = Callable[[dict[str, Any]], str]
|
|
14
|
+
ToolAliasTransform = Callable[[dict[str, Any]], dict[str, Any]]
|
|
15
|
+
_PATH_BASE_ENUM = ["active_workdir", "workspace_root"]
|
|
16
|
+
_UNKNOWN_TOOL_SUGGESTION_THRESHOLD = 0.72
|
|
17
|
+
_UNKNOWN_TOOL_MAX_SUGGESTIONS = 3
|
|
18
|
+
REPORT_BLOCKER_MAX_MESSAGE_CHARS = 16_384
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _path_base_property(*, description: str | None = None) -> dict[str, Any]:
|
|
22
|
+
payload: dict[str, Any] = {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"enum": list(_PATH_BASE_ENUM),
|
|
25
|
+
"default": "active_workdir",
|
|
26
|
+
}
|
|
27
|
+
if description:
|
|
28
|
+
payload["description"] = description
|
|
29
|
+
return payload
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _cwd_base_property() -> dict[str, Any]:
|
|
33
|
+
return _path_base_property(
|
|
34
|
+
description=(
|
|
35
|
+
"Resolve a relative cwd from the live active_workdir (default) or from the immutable "
|
|
36
|
+
"workspace_root."
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _service_readiness_property() -> dict[str, Any]:
|
|
42
|
+
return {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"description": (
|
|
45
|
+
"Optional bounded readiness probe. type=process_alive, tcp, unix_socket, or command. "
|
|
46
|
+
"tcp uses host+port; unix_socket uses path; command uses a policy-checked command."
|
|
47
|
+
),
|
|
48
|
+
"properties": {
|
|
49
|
+
"type": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"enum": ["process_alive", "tcp", "unix_socket", "command"],
|
|
52
|
+
"default": "process_alive",
|
|
53
|
+
},
|
|
54
|
+
"host": {"type": "string", "default": "localhost"},
|
|
55
|
+
"port": {"type": "integer", "minimum": 1, "maximum": 65535},
|
|
56
|
+
"path": {"type": "string"},
|
|
57
|
+
"command": {"type": "string"},
|
|
58
|
+
"timeout_s": {"type": "number", "default": 5.0, "minimum": 0, "maximum": 30},
|
|
59
|
+
"interval_s": {"type": "number", "default": 0.1, "minimum": 0.02, "maximum": 2},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _truncate_inline(text: str, *, max_chars: int = 96) -> str:
|
|
65
|
+
normalized = " ".join(str(text).split())
|
|
66
|
+
if len(normalized) <= max_chars:
|
|
67
|
+
return normalized
|
|
68
|
+
if max_chars <= 3:
|
|
69
|
+
return normalized[:max_chars]
|
|
70
|
+
return normalized[: max_chars - 3] + "..."
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _preview_shell_run(args: dict[str, Any]) -> str:
|
|
74
|
+
cmd = str(args.get("cmd") or "").strip()
|
|
75
|
+
return _truncate_inline(cmd, max_chars=120) or "-"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _preview_shell_output(args: dict[str, Any]) -> str:
|
|
79
|
+
process_id = str(args.get("process_id") or "").strip()
|
|
80
|
+
since = args.get("since")
|
|
81
|
+
if since is None:
|
|
82
|
+
return _truncate_inline(process_id, max_chars=120) or "-"
|
|
83
|
+
return _truncate_inline(f"{process_id} since={since}", max_chars=120) or "-"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _preview_shell_kill(args: dict[str, Any]) -> str:
|
|
87
|
+
return _truncate_inline(str(args.get("process_id") or "").strip(), max_chars=120) or "-"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _preview_shell_service_id(args: dict[str, Any]) -> str:
|
|
91
|
+
return _truncate_inline(str(args.get("service_id") or "").strip(), max_chars=120) or "-"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _preview_fs_read_lines(args: dict[str, Any]) -> str:
|
|
95
|
+
path = str(args.get("path") or "").strip()
|
|
96
|
+
start_line = args.get("start_line")
|
|
97
|
+
end_line = args.get("end_line")
|
|
98
|
+
max_lines = args.get("max_lines")
|
|
99
|
+
if end_line is None:
|
|
100
|
+
line_part = f"{start_line}"
|
|
101
|
+
else:
|
|
102
|
+
line_part = f"{start_line}-{end_line}"
|
|
103
|
+
preview = f"{path}:{line_part}"
|
|
104
|
+
if max_lines is not None:
|
|
105
|
+
preview += f" (max {max_lines})"
|
|
106
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _preview_verify_run(args: dict[str, Any]) -> str:
|
|
110
|
+
commands = args.get("commands")
|
|
111
|
+
if not isinstance(commands, list) or not commands:
|
|
112
|
+
return "configured commands"
|
|
113
|
+
first = str(commands[0]).strip()
|
|
114
|
+
if len(commands) == 1:
|
|
115
|
+
return _truncate_inline(first, max_chars=120) or "-"
|
|
116
|
+
return _truncate_inline(f"{first} (+{len(commands) - 1} more)", max_chars=120) or "-"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _preview_image_generate(args: dict[str, Any]) -> str:
|
|
120
|
+
output_path = str(args.get("output_path") or "").strip()
|
|
121
|
+
prompt = _truncate_inline(str(args.get("prompt") or ""), max_chars=72)
|
|
122
|
+
count = args.get("count", 1)
|
|
123
|
+
return _truncate_inline(f"{output_path} x{count} - {prompt}", max_chars=120) or "-"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _summary_image_generate(parsed: dict[str, Any]) -> str:
|
|
127
|
+
if is_tool_unavailable_result(parsed):
|
|
128
|
+
return "Image generation unavailable."
|
|
129
|
+
error = str(parsed.get("error") or "").strip()
|
|
130
|
+
if error:
|
|
131
|
+
return _truncate_inline(f"Image generation failed: {error}", max_chars=160)
|
|
132
|
+
paths = parsed.get("output_paths")
|
|
133
|
+
if isinstance(paths, list) and paths:
|
|
134
|
+
return _truncate_inline(
|
|
135
|
+
"Generated image asset(s): " + ", ".join(str(path) for path in paths),
|
|
136
|
+
max_chars=160,
|
|
137
|
+
)
|
|
138
|
+
return "Image generation finished."
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _preview_git_history(args: dict[str, Any]) -> str:
|
|
142
|
+
mode = str(args.get("mode") or "").strip()
|
|
143
|
+
if mode == "log":
|
|
144
|
+
ref = str(args.get("ref") or "HEAD").strip() or "HEAD"
|
|
145
|
+
path = str(args.get("path") or "").strip()
|
|
146
|
+
grep = str(args.get("grep") or "").strip()
|
|
147
|
+
preview = f"log {ref}"
|
|
148
|
+
if path:
|
|
149
|
+
preview += f" -- {path}"
|
|
150
|
+
if grep:
|
|
151
|
+
preview += f" grep={grep}"
|
|
152
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
153
|
+
if mode == "show":
|
|
154
|
+
commit = str(args.get("commit") or args.get("ref") or "").strip()
|
|
155
|
+
path = str(args.get("path") or "").strip()
|
|
156
|
+
preview = f"show {commit}"
|
|
157
|
+
if path:
|
|
158
|
+
preview += f" -- {path}"
|
|
159
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
160
|
+
if mode == "blame":
|
|
161
|
+
path = str(args.get("path") or "").strip()
|
|
162
|
+
start_line = args.get("start_line")
|
|
163
|
+
end_line = args.get("end_line")
|
|
164
|
+
preview = f"blame {path}:{start_line}-{end_line}"
|
|
165
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
166
|
+
return _truncate_inline(mode, max_chars=120) or "-"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _preview_symbol_search(args: dict[str, Any]) -> str:
|
|
170
|
+
query = str(args.get("query") or "").strip()
|
|
171
|
+
kind = str(args.get("kind") or "").strip()
|
|
172
|
+
exact = bool(args.get("exact", False))
|
|
173
|
+
preview = query
|
|
174
|
+
if kind:
|
|
175
|
+
preview += f" kind={kind}"
|
|
176
|
+
if exact:
|
|
177
|
+
preview += " exact"
|
|
178
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _preview_source_destination(args: dict[str, Any]) -> str:
|
|
182
|
+
source_path = str(args.get("source_path") or "").strip()
|
|
183
|
+
destination_path = str(args.get("destination_path") or "").strip()
|
|
184
|
+
preview = f"{source_path} -> {destination_path}"
|
|
185
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _preview_single_path(args: dict[str, Any]) -> str:
|
|
189
|
+
path = str(args.get("path") or "").strip()
|
|
190
|
+
return _truncate_inline(path, max_chars=120) or "-"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _preview_pattern(args: dict[str, Any]) -> str:
|
|
194
|
+
pattern = str(args.get("pattern") or "").strip()
|
|
195
|
+
return _truncate_inline(pattern, max_chars=120) or "-"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _preview_web_fetch(args: dict[str, Any]) -> str:
|
|
199
|
+
url = str(args.get("url") or "").strip()
|
|
200
|
+
max_chars = args.get("max_chars")
|
|
201
|
+
if max_chars is None:
|
|
202
|
+
return _truncate_inline(url, max_chars=120) or "-"
|
|
203
|
+
return _truncate_inline(f"{url} (max_chars={max_chars})", max_chars=120) or "-"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _preview_web_search(args: dict[str, Any]) -> str:
|
|
207
|
+
query = str(args.get("query") or "").strip()
|
|
208
|
+
allowed_domains = args.get("allowed_domains")
|
|
209
|
+
if isinstance(allowed_domains, list) and allowed_domains:
|
|
210
|
+
preview = f"{query} domains={','.join(str(item).strip() for item in allowed_domains[:3])}"
|
|
211
|
+
if len(allowed_domains) > 3:
|
|
212
|
+
preview += f" (+{len(allowed_domains) - 3})"
|
|
213
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
214
|
+
return _truncate_inline(query, max_chars=120) or "-"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _preview_skill_read(args: dict[str, Any]) -> str:
|
|
218
|
+
name = str(args.get("name") or "").strip()
|
|
219
|
+
path = str(args.get("path") or "").strip()
|
|
220
|
+
preview = name
|
|
221
|
+
if path:
|
|
222
|
+
preview += f" :: {path}"
|
|
223
|
+
return _truncate_inline(preview, max_chars=120) or "-"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _summary_subagent_run(parsed: dict[str, Any]) -> str:
|
|
227
|
+
subagent_name = str(parsed.get("subagent") or parsed.get("name") or "?")
|
|
228
|
+
sandbox_obj = parsed.get("sandbox")
|
|
229
|
+
sandbox = sandbox_obj if isinstance(sandbox_obj, dict) else {}
|
|
230
|
+
mode = str(sandbox.get("mode") or "-")
|
|
231
|
+
tools_obj = sandbox.get("tools")
|
|
232
|
+
tool_count = len(tools_obj) if isinstance(tools_obj, list) else 0
|
|
233
|
+
result_text = str(parsed.get("result") or parsed.get("final_text") or "")
|
|
234
|
+
result_len = len(result_text)
|
|
235
|
+
if "error" in parsed:
|
|
236
|
+
msg = _truncate_inline(str(parsed.get("error") or ""), max_chars=130)
|
|
237
|
+
return f'Subagent "{subagent_name}" failed: {msg}'
|
|
238
|
+
details: list[str] = []
|
|
239
|
+
if tool_count > 0:
|
|
240
|
+
details.append(f"tools={tool_count}")
|
|
241
|
+
if result_len > 0:
|
|
242
|
+
details.append(f"result={result_len} chars")
|
|
243
|
+
suffix = f" ({', '.join(details)})" if details else ""
|
|
244
|
+
return f'Subagent "{subagent_name}" mode={mode}{suffix}.'
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _summary_subagent_background(parsed: dict[str, Any]) -> str:
|
|
248
|
+
run_id = str(parsed.get("run_id") or "").strip()
|
|
249
|
+
state = str(parsed.get("state") or "").strip()
|
|
250
|
+
pending = parsed.get("pending_run_ids")
|
|
251
|
+
if run_id:
|
|
252
|
+
suffix = f" state={state}" if state else ""
|
|
253
|
+
return f"Background subagent {run_id[:12]}{suffix}."
|
|
254
|
+
if isinstance(pending, list) and pending:
|
|
255
|
+
return f"Background subagents: {len(pending)} still pending."
|
|
256
|
+
return "Background subagent operation completed."
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _summary_fs_read(parsed: dict[str, Any]) -> str:
|
|
260
|
+
path = str(parsed.get("path") or "?")
|
|
261
|
+
content = str(parsed.get("content") or "")
|
|
262
|
+
truncated = bool(parsed.get("truncated"))
|
|
263
|
+
if parsed.get("derived_artifact"):
|
|
264
|
+
size_bytes = parsed.get("size_bytes")
|
|
265
|
+
size_note = f"; {size_bytes} bytes on disk" if isinstance(size_bytes, int) else ""
|
|
266
|
+
return f'Sampled derived artifact "{path}" (head only{size_note}; content withheld).'
|
|
267
|
+
trunc_note = ", truncated" if truncated else ""
|
|
268
|
+
return f'Loaded "{path}" ({len(content)} chars{trunc_note}).'
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _summary_fs_read_lines(parsed: dict[str, Any]) -> str:
|
|
272
|
+
path = str(parsed.get("path") or "?")
|
|
273
|
+
start_line = parsed.get("start_line")
|
|
274
|
+
end_line = parsed.get("end_line")
|
|
275
|
+
truncated = bool(parsed.get("truncated"))
|
|
276
|
+
trunc_note = ", truncated" if truncated else ""
|
|
277
|
+
if isinstance(start_line, int) and isinstance(end_line, int) and end_line >= start_line:
|
|
278
|
+
count = end_line - start_line + 1
|
|
279
|
+
if count == 1:
|
|
280
|
+
return f'Loaded "{path}" line {start_line} (1 line{trunc_note}).'
|
|
281
|
+
return f'Loaded "{path}" lines {start_line}-{end_line} ({count} lines{trunc_note}).'
|
|
282
|
+
content = str(parsed.get("content") or "")
|
|
283
|
+
return f'Loaded "{path}" ({len(content)} chars{trunc_note}).'
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _summary_fs_edit(parsed: dict[str, Any]) -> str:
|
|
287
|
+
path = str(parsed.get("path") or "?")
|
|
288
|
+
applied_edits = parsed.get("applied_edits")
|
|
289
|
+
changed = bool(parsed.get("changed"))
|
|
290
|
+
if changed:
|
|
291
|
+
size = parsed.get("bytes")
|
|
292
|
+
return f'Edited "{path}" ({applied_edits} edit(s), {size} bytes).'
|
|
293
|
+
return f'Edited "{path}" ({applied_edits} edit(s), no content change).'
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _summary_fs_move(parsed: dict[str, Any]) -> str:
|
|
297
|
+
source_path = str(parsed.get("source_path") or "?")
|
|
298
|
+
destination_path = str(parsed.get("destination_path") or "?")
|
|
299
|
+
size = parsed.get("bytes")
|
|
300
|
+
overwritten = bool(parsed.get("overwritten"))
|
|
301
|
+
overwrite_note = ", replaced existing destination" if overwritten else ""
|
|
302
|
+
return f'Moved "{source_path}" -> "{destination_path}" ({size} bytes{overwrite_note}).'
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _summary_fs_copy(parsed: dict[str, Any]) -> str:
|
|
306
|
+
source_path = str(parsed.get("source_path") or "?")
|
|
307
|
+
destination_path = str(parsed.get("destination_path") or "?")
|
|
308
|
+
size = parsed.get("bytes")
|
|
309
|
+
overwritten = bool(parsed.get("overwritten"))
|
|
310
|
+
overwrite_note = ", replaced existing destination" if overwritten else ""
|
|
311
|
+
return f'Copied "{source_path}" -> "{destination_path}" ({size} bytes{overwrite_note}).'
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _summary_fs_delete(parsed: dict[str, Any]) -> str:
|
|
315
|
+
path = str(parsed.get("path") or "?")
|
|
316
|
+
size = parsed.get("bytes")
|
|
317
|
+
return f'Deleted "{path}" ({size} bytes).'
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _summary_fs_write(parsed: dict[str, Any]) -> str:
|
|
321
|
+
path = str(parsed.get("path") or "?")
|
|
322
|
+
size = parsed.get("bytes")
|
|
323
|
+
return f'Updated "{path}" ({size} bytes).'
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _summary_fs_mkdir(parsed: dict[str, Any]) -> str:
|
|
327
|
+
path = str(parsed.get("path") or "?")
|
|
328
|
+
if bool(parsed.get("already_exists")):
|
|
329
|
+
return f'Directory "{path}" already exists.'
|
|
330
|
+
return f'Created directory "{path}".'
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _summary_fs_list(parsed: dict[str, Any]) -> str:
|
|
334
|
+
entries = parsed.get("entries")
|
|
335
|
+
count = len(entries) if isinstance(entries, list) else 0
|
|
336
|
+
truncated = bool(parsed.get("truncated"))
|
|
337
|
+
trunc_note = ", truncated" if truncated else ""
|
|
338
|
+
return f"Found {count} file(s){trunc_note}."
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _summary_symbol_search(parsed: dict[str, Any]) -> str:
|
|
342
|
+
matches = parsed.get("matches")
|
|
343
|
+
count = len(matches) if isinstance(matches, list) else 0
|
|
344
|
+
query = _truncate_inline(str(parsed.get("query") or ""), max_chars=44)
|
|
345
|
+
truncated = bool(parsed.get("truncated"))
|
|
346
|
+
trunc_note = ", truncated" if truncated else ""
|
|
347
|
+
return f'Found {count} symbol match(es) for "{query}"{trunc_note}.'
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _summary_test_discover(parsed: dict[str, Any]) -> str:
|
|
351
|
+
tests = parsed.get("candidate_tests")
|
|
352
|
+
commands = parsed.get("candidate_commands")
|
|
353
|
+
test_count = len(tests) if isinstance(tests, list) else 0
|
|
354
|
+
command_count = len(commands) if isinstance(commands, list) else 0
|
|
355
|
+
frameworks = parsed.get("frameworks")
|
|
356
|
+
framework_text = ""
|
|
357
|
+
if isinstance(frameworks, list) and frameworks:
|
|
358
|
+
framework_text = " (" + ", ".join(str(item) for item in frameworks[:3]) + ")"
|
|
359
|
+
return (
|
|
360
|
+
f"Found {test_count} likely test file(s) and "
|
|
361
|
+
f"{command_count} candidate command(s){framework_text}."
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _summary_repo_map(parsed: dict[str, Any]) -> str:
|
|
366
|
+
related = parsed.get("related_files")
|
|
367
|
+
edges = parsed.get("import_edges")
|
|
368
|
+
tests = parsed.get("candidate_tests")
|
|
369
|
+
related_count = len(related) if isinstance(related, list) else 0
|
|
370
|
+
edge_count = len(edges) if isinstance(edges, list) else 0
|
|
371
|
+
test_count = len(tests) if isinstance(tests, list) else 0
|
|
372
|
+
return (
|
|
373
|
+
f"Mapped {related_count} related file(s), {edge_count} import edge(s), "
|
|
374
|
+
f"and {test_count} likely test file(s)."
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _summary_search_rg(parsed: dict[str, Any]) -> str:
|
|
379
|
+
matches = parsed.get("matches")
|
|
380
|
+
count = len(matches) if isinstance(matches, list) else 0
|
|
381
|
+
pattern = _truncate_inline(str(parsed.get("pattern") or ""), max_chars=44)
|
|
382
|
+
return f'Found {count} matches for "{pattern}".'
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _summary_history_search(parsed: dict[str, Any]) -> str:
|
|
386
|
+
matches = parsed.get("matches")
|
|
387
|
+
count = len(matches) if isinstance(matches, list) else 0
|
|
388
|
+
pattern = _truncate_inline(str(parsed.get("pattern") or ""), max_chars=44)
|
|
389
|
+
truncated = bool(parsed.get("truncated"))
|
|
390
|
+
trunc_note = ", truncated" if truncated else ""
|
|
391
|
+
return f'Found {count} history match(es) for "{pattern}"{trunc_note}.'
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _summary_skill_read(parsed: dict[str, Any]) -> str:
|
|
395
|
+
name = str(parsed.get("name") or parsed.get("bundle_name") or "?")
|
|
396
|
+
path = str(parsed.get("path") or "SKILL.md")
|
|
397
|
+
content = str(parsed.get("content") or "")
|
|
398
|
+
return f'Loaded skill "{name}" file "{path}" ({len(content)} chars).'
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _summary_web_fetch(parsed: dict[str, Any]) -> str:
|
|
402
|
+
final_url = _truncate_inline(
|
|
403
|
+
str(parsed.get("final_url") or parsed.get("url") or ""), max_chars=84
|
|
404
|
+
)
|
|
405
|
+
status_code = parsed.get("status_code")
|
|
406
|
+
content_type = _truncate_inline(str(parsed.get("content_type") or ""), max_chars=28)
|
|
407
|
+
content = str(parsed.get("content") or "")
|
|
408
|
+
truncated = bool(parsed.get("truncated"))
|
|
409
|
+
trunc_note = ", truncated" if truncated else ""
|
|
410
|
+
return (
|
|
411
|
+
f"Fetched {final_url} status={status_code} type={content_type} "
|
|
412
|
+
f"content={len(content)} chars{trunc_note}."
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _summary_web_search(parsed: dict[str, Any]) -> str:
|
|
417
|
+
answer = str(parsed.get("answer") or "")
|
|
418
|
+
sources = parsed.get("sources")
|
|
419
|
+
source_count = len(sources) if isinstance(sources, list) else 0
|
|
420
|
+
truncated = bool(parsed.get("sources_truncated"))
|
|
421
|
+
trunc_note = ", truncated" if truncated else ""
|
|
422
|
+
backend = _truncate_inline(str(parsed.get("backend") or ""), max_chars=24)
|
|
423
|
+
model = _truncate_inline(str(parsed.get("model") or ""), max_chars=32)
|
|
424
|
+
details = []
|
|
425
|
+
if backend:
|
|
426
|
+
details.append(f"backend={backend}")
|
|
427
|
+
if model:
|
|
428
|
+
details.append(f"model={model}")
|
|
429
|
+
details_note = f" {' '.join(details)}" if details else ""
|
|
430
|
+
return (
|
|
431
|
+
f"Web search returned {source_count} source(s){trunc_note}; "
|
|
432
|
+
f"answer={len(answer)} chars.{details_note}"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _summary_verify_run(parsed: dict[str, Any]) -> str:
|
|
437
|
+
summary = _truncate_inline(str(parsed.get("summary") or ""), max_chars=120)
|
|
438
|
+
primary_failure = parsed.get("primary_failure")
|
|
439
|
+
hint = ""
|
|
440
|
+
if isinstance(primary_failure, dict):
|
|
441
|
+
snippet = str(primary_failure.get("snippet") or "").strip()
|
|
442
|
+
if snippet:
|
|
443
|
+
hint = f"Hint: {_truncate_inline(snippet, max_chars=110)}"
|
|
444
|
+
artifact_path = str(parsed.get("artifact_path") or "").strip()
|
|
445
|
+
if artifact_path:
|
|
446
|
+
prefix = " ".join(part for part in (summary, hint) if part)
|
|
447
|
+
if prefix:
|
|
448
|
+
return f"{prefix} Artifact: {artifact_path}"
|
|
449
|
+
return f"Artifact: {artifact_path}"
|
|
450
|
+
artifact_saved = bool(parsed.get("artifact_saved"))
|
|
451
|
+
artifact_readable_via_fs = bool(parsed.get("artifact_readable_via_fs"))
|
|
452
|
+
artifact_location = str(parsed.get("artifact_location") or "").strip()
|
|
453
|
+
if artifact_saved and not artifact_readable_via_fs:
|
|
454
|
+
if artifact_location == "external_session_store":
|
|
455
|
+
artifact_note = "Artifact saved externally (not readable via fs)."
|
|
456
|
+
else:
|
|
457
|
+
artifact_note = "Artifact saved outside the workspace (not readable via fs)."
|
|
458
|
+
prefix = " ".join(part for part in (summary, hint) if part)
|
|
459
|
+
if prefix:
|
|
460
|
+
return f"{prefix} {artifact_note}"
|
|
461
|
+
return artifact_note
|
|
462
|
+
if artifact_saved:
|
|
463
|
+
prefix = " ".join(part for part in (summary, hint) if part)
|
|
464
|
+
if prefix:
|
|
465
|
+
return f"{prefix} Artifact saved."
|
|
466
|
+
return "Artifact saved."
|
|
467
|
+
return " ".join(part for part in (summary, hint) if part) or "Verification finished."
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _summary_shell_run(parsed: dict[str, Any]) -> str:
|
|
471
|
+
exit_code = parsed.get("exit_code")
|
|
472
|
+
stdout = str(parsed.get("stdout") or "")
|
|
473
|
+
stderr = str(parsed.get("stderr") or "")
|
|
474
|
+
summary = f"Command exited with code {exit_code}."
|
|
475
|
+
summary += f" stdout={len(stdout)} chars, stderr={len(stderr)} chars."
|
|
476
|
+
if stderr.strip():
|
|
477
|
+
first_err = _truncate_inline(stderr.strip().splitlines()[0], max_chars=90)
|
|
478
|
+
summary += f" stderr preview: {first_err}"
|
|
479
|
+
return summary
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _summary_shell_background(parsed: dict[str, Any]) -> str:
|
|
483
|
+
process_id = str(parsed.get("process_id") or "?")
|
|
484
|
+
status = str(parsed.get("status") or "?")
|
|
485
|
+
return f'Started background process "{process_id}" (status={status}).'
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _summary_shell_output(parsed: dict[str, Any]) -> str:
|
|
489
|
+
process_id = str(parsed.get("process_id") or "?")
|
|
490
|
+
status = str(parsed.get("status") or "?")
|
|
491
|
+
if status == "unknown_process_id" or bool(parsed.get("unknown_process_id")):
|
|
492
|
+
known = parsed.get("known_process_ids")
|
|
493
|
+
known_count = len(known) if isinstance(known, list) else 0
|
|
494
|
+
return (
|
|
495
|
+
f'Unknown background process "{process_id}"; '
|
|
496
|
+
f"{known_count} process id(s) currently tracked."
|
|
497
|
+
)
|
|
498
|
+
lines = parsed.get("lines")
|
|
499
|
+
line_count = len(lines) if isinstance(lines, list) else 0
|
|
500
|
+
dropped = int(parsed.get("dropped_lines") or 0)
|
|
501
|
+
drop_note = f", {dropped} dropped" if dropped > 0 else ""
|
|
502
|
+
return f'Read {line_count} new line(s) from "{process_id}" (status={status}{drop_note}).'
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _summary_shell_wait(parsed: dict[str, Any]) -> str:
|
|
506
|
+
process_id = str(parsed.get("process_id") or "?")
|
|
507
|
+
status = str(parsed.get("status") or "?")
|
|
508
|
+
if status == "unknown_process_id" or bool(parsed.get("unknown_process_id")):
|
|
509
|
+
known = parsed.get("known_process_ids")
|
|
510
|
+
known_count = len(known) if isinstance(known, list) else 0
|
|
511
|
+
return (
|
|
512
|
+
f'Unknown background process "{process_id}"; '
|
|
513
|
+
f"{known_count} process id(s) currently tracked."
|
|
514
|
+
)
|
|
515
|
+
lines = parsed.get("lines")
|
|
516
|
+
line_count = len(lines) if isinstance(lines, list) else 0
|
|
517
|
+
timed_out = bool(parsed.get("timed_out"))
|
|
518
|
+
timeout_note = ", timed out" if timed_out else ""
|
|
519
|
+
return f'Waited for "{process_id}" and read {line_count} new line(s) (status={status}{timeout_note}).'
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _summary_shell_kill(parsed: dict[str, Any]) -> str:
|
|
523
|
+
process_id = str(parsed.get("process_id") or "?")
|
|
524
|
+
status = str(parsed.get("status") or "?")
|
|
525
|
+
exit_code = parsed.get("exit_code")
|
|
526
|
+
code_note = f", exit_code={exit_code}" if exit_code is not None else ""
|
|
527
|
+
return f'Terminated background process "{process_id}" (status={status}{code_note}).'
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _summary_shell_list(parsed: dict[str, Any]) -> str:
|
|
531
|
+
processes = parsed.get("processes")
|
|
532
|
+
count = len(processes) if isinstance(processes, list) else 0
|
|
533
|
+
if count == 0:
|
|
534
|
+
return "No background processes."
|
|
535
|
+
running = sum(
|
|
536
|
+
1
|
|
537
|
+
for process in (processes or [])
|
|
538
|
+
if isinstance(process, dict) and process.get("status") == "running"
|
|
539
|
+
)
|
|
540
|
+
return f"Listed {count} background process(es) ({running} running)."
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _summary_shell_service(parsed: dict[str, Any]) -> str:
|
|
544
|
+
service_id = str(parsed.get("service_id") or "?")
|
|
545
|
+
status = str(parsed.get("status") or "?")
|
|
546
|
+
readiness = parsed.get("readiness") if isinstance(parsed.get("readiness"), dict) else {}
|
|
547
|
+
readiness_status = str(readiness.get("status") or "?")
|
|
548
|
+
return f'Durable service "{service_id}" status={status}, readiness={readiness_status}.'
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _summary_session_set_workdir(parsed: dict[str, Any]) -> str:
|
|
552
|
+
relpath = str(parsed.get("active_workdir_relpath") or ".").strip() or "."
|
|
553
|
+
return f"Active workdir set to {relpath}."
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _summary_git_status(parsed: dict[str, Any]) -> str:
|
|
557
|
+
status_text = str(parsed.get("status") or "")
|
|
558
|
+
lines = [line for line in status_text.splitlines() if line.strip()]
|
|
559
|
+
return f"Collected git status ({len(lines)} non-empty lines)."
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _summary_git_history(parsed: dict[str, Any]) -> str:
|
|
563
|
+
mode = str(parsed.get("mode") or "")
|
|
564
|
+
if mode == "log":
|
|
565
|
+
commits = parsed.get("commits")
|
|
566
|
+
count = len(commits) if isinstance(commits, list) else 0
|
|
567
|
+
truncated = bool(parsed.get("truncated"))
|
|
568
|
+
trunc_note = ", truncated" if truncated else ""
|
|
569
|
+
return f"Loaded git history ({count} commit(s){trunc_note})."
|
|
570
|
+
if mode == "show":
|
|
571
|
+
commit_obj = parsed.get("commit")
|
|
572
|
+
short_commit = ""
|
|
573
|
+
if isinstance(commit_obj, dict):
|
|
574
|
+
short_commit = str(commit_obj.get("short_commit") or "")
|
|
575
|
+
patch_excerpt = str(parsed.get("patch_excerpt") or "")
|
|
576
|
+
truncated = bool(parsed.get("patch_truncated"))
|
|
577
|
+
trunc_note = ", truncated" if truncated else ""
|
|
578
|
+
label = short_commit or "commit"
|
|
579
|
+
return f"Loaded commit {label} ({len(patch_excerpt)} chars{trunc_note})."
|
|
580
|
+
if mode == "blame":
|
|
581
|
+
path = str(parsed.get("path") or "?")
|
|
582
|
+
start_line = parsed.get("start_line")
|
|
583
|
+
end_line = parsed.get("end_line")
|
|
584
|
+
lines = parsed.get("lines")
|
|
585
|
+
count = len(lines) if isinstance(lines, list) else 0
|
|
586
|
+
return f'Loaded blame for "{path}" lines {start_line}-{end_line} ({count} line(s)).'
|
|
587
|
+
return "Loaded git history."
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _summary_git_diff(parsed: dict[str, Any]) -> str:
|
|
591
|
+
diff_text = str(parsed.get("diff") or "")
|
|
592
|
+
files = diff_text.count("diff --git")
|
|
593
|
+
return f"Collected git diff ({len(diff_text)} chars, about {files} file(s))."
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _summary_git_apply_patch(parsed: dict[str, Any]) -> str:
|
|
597
|
+
if parsed.get("applied") is True:
|
|
598
|
+
return "Patch applied successfully."
|
|
599
|
+
keys = ", ".join(sorted(str(k) for k in parsed.keys())[:6])
|
|
600
|
+
return f"Output keys: {keys or '-'}."
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def _fs_edit_op_variant(
|
|
604
|
+
*,
|
|
605
|
+
ops: tuple[str, ...],
|
|
606
|
+
required: tuple[str, ...],
|
|
607
|
+
include_content: bool,
|
|
608
|
+
include_target: bool,
|
|
609
|
+
include_replacement: bool,
|
|
610
|
+
extra_properties: dict[str, Any] | None = None,
|
|
611
|
+
) -> dict[str, Any]:
|
|
612
|
+
properties: dict[str, Any] = {
|
|
613
|
+
"op": {"type": "string", "enum": list(ops)},
|
|
614
|
+
}
|
|
615
|
+
if include_content:
|
|
616
|
+
properties["content"] = {"type": "string"}
|
|
617
|
+
if include_target:
|
|
618
|
+
properties["target"] = {"type": "string", "minLength": 1}
|
|
619
|
+
properties["expected_match_count"] = {"type": "integer", "minimum": 0}
|
|
620
|
+
if include_replacement:
|
|
621
|
+
properties["replacement"] = {"type": "string"}
|
|
622
|
+
if extra_properties:
|
|
623
|
+
properties.update(extra_properties)
|
|
624
|
+
return {
|
|
625
|
+
"type": "object",
|
|
626
|
+
"additionalProperties": False,
|
|
627
|
+
"properties": properties,
|
|
628
|
+
"required": list(required),
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _fs_edit_parameters() -> dict[str, Any]:
|
|
633
|
+
return {
|
|
634
|
+
"type": "object",
|
|
635
|
+
"additionalProperties": False,
|
|
636
|
+
"properties": {
|
|
637
|
+
"path": {"type": "string"},
|
|
638
|
+
"path_base": _path_base_property(),
|
|
639
|
+
"edits": {
|
|
640
|
+
"type": "array",
|
|
641
|
+
"minItems": 1,
|
|
642
|
+
"items": {
|
|
643
|
+
"anyOf": [
|
|
644
|
+
_fs_edit_op_variant(
|
|
645
|
+
ops=("replace", "replace_exact"),
|
|
646
|
+
required=("op", "target", "replacement"),
|
|
647
|
+
include_content=False,
|
|
648
|
+
include_target=True,
|
|
649
|
+
include_replacement=True,
|
|
650
|
+
),
|
|
651
|
+
_fs_edit_op_variant(
|
|
652
|
+
ops=("insert_before_exact", "insert_after_exact"),
|
|
653
|
+
required=("op", "target", "content"),
|
|
654
|
+
include_content=True,
|
|
655
|
+
include_target=True,
|
|
656
|
+
include_replacement=False,
|
|
657
|
+
),
|
|
658
|
+
_fs_edit_op_variant(
|
|
659
|
+
ops=("append", "prepend"),
|
|
660
|
+
required=("op", "content"),
|
|
661
|
+
include_content=True,
|
|
662
|
+
include_target=False,
|
|
663
|
+
include_replacement=False,
|
|
664
|
+
),
|
|
665
|
+
_fs_edit_op_variant(
|
|
666
|
+
ops=("replace_lines",),
|
|
667
|
+
required=("op", "start_line", "end_line", "replacement"),
|
|
668
|
+
include_content=False,
|
|
669
|
+
include_target=False,
|
|
670
|
+
include_replacement=True,
|
|
671
|
+
extra_properties={
|
|
672
|
+
"start_line": {"type": "integer", "minimum": 1},
|
|
673
|
+
"end_line": {"type": "integer", "minimum": 1},
|
|
674
|
+
"expected_old": {
|
|
675
|
+
"type": "string",
|
|
676
|
+
"description": (
|
|
677
|
+
"Optional exact old text for replace_lines; the edit fails "
|
|
678
|
+
"closed if the selected line range no longer matches."
|
|
679
|
+
),
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
),
|
|
683
|
+
_fs_edit_op_variant(
|
|
684
|
+
ops=("insert_before_line", "insert_after_line"),
|
|
685
|
+
required=("op", "line", "content"),
|
|
686
|
+
include_content=True,
|
|
687
|
+
include_target=False,
|
|
688
|
+
include_replacement=False,
|
|
689
|
+
extra_properties={
|
|
690
|
+
"line": {"type": "integer", "minimum": 1},
|
|
691
|
+
},
|
|
692
|
+
),
|
|
693
|
+
]
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
"required": ["path", "edits"],
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
@dataclass(frozen=True)
|
|
702
|
+
class RichToolMetadata:
|
|
703
|
+
display_name: str
|
|
704
|
+
reasoning_hint: str
|
|
705
|
+
action_hint: str
|
|
706
|
+
fallback_hint: str
|
|
707
|
+
input_preview_formatter: ToolFormatter | None = None
|
|
708
|
+
output_summary_formatter: ToolFormatter | None = None
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
@dataclass(frozen=True)
|
|
712
|
+
class BuiltinToolMetadata:
|
|
713
|
+
name: str
|
|
714
|
+
description: str
|
|
715
|
+
parameters: dict[str, Any]
|
|
716
|
+
categories: tuple[str, ...]
|
|
717
|
+
rich: RichToolMetadata
|
|
718
|
+
built_in_subagent_exposure: str = "hidden"
|
|
719
|
+
optional: bool = False
|
|
720
|
+
optional_unavailable_reason: str | None = None
|
|
721
|
+
|
|
722
|
+
def copied_parameters(self) -> dict[str, Any]:
|
|
723
|
+
return copy.deepcopy(self.parameters)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
_BUILTIN_TOOL_METADATA: tuple[BuiltinToolMetadata, ...] = (
|
|
727
|
+
BuiltinToolMetadata(
|
|
728
|
+
name="report_blocker",
|
|
729
|
+
description=(
|
|
730
|
+
"Report a concrete obstacle that prevents this top-level execute turn from "
|
|
731
|
+
"completing safely. Supply the user-facing explanation in message and use this "
|
|
732
|
+
"as the final tool call when no available tool can resolve the obstacle. The host "
|
|
733
|
+
"uses the successful tool result as a structured blocker signal; words in message "
|
|
734
|
+
"never control routing or completion."
|
|
735
|
+
),
|
|
736
|
+
parameters={
|
|
737
|
+
"type": "object",
|
|
738
|
+
"properties": {
|
|
739
|
+
"message": {
|
|
740
|
+
"type": "string",
|
|
741
|
+
"description": (
|
|
742
|
+
"User-facing explanation of what prevents completion. Any language, "
|
|
743
|
+
"script, or punctuation is allowed; maxLength is transport protection "
|
|
744
|
+
"only."
|
|
745
|
+
),
|
|
746
|
+
"maxLength": REPORT_BLOCKER_MAX_MESSAGE_CHARS,
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
"required": ["message"],
|
|
750
|
+
},
|
|
751
|
+
categories=("session",),
|
|
752
|
+
rich=RichToolMetadata(
|
|
753
|
+
display_name="Report Blocker",
|
|
754
|
+
reasoning_hint="A concrete obstacle prevents safe completion of this turn.",
|
|
755
|
+
action_hint="Report the obstacle as the final structured turn action.",
|
|
756
|
+
fallback_hint="Continue with available tools when the obstacle can still be resolved.",
|
|
757
|
+
),
|
|
758
|
+
built_in_subagent_exposure="hidden",
|
|
759
|
+
optional=True,
|
|
760
|
+
optional_unavailable_reason="completion gating is not active for this runtime",
|
|
761
|
+
),
|
|
762
|
+
BuiltinToolMetadata(
|
|
763
|
+
name="switch_mode",
|
|
764
|
+
description=(
|
|
765
|
+
"Propose switching this chat session to another persona mode: code, "
|
|
766
|
+
"architect, ask, or debug. The user must approve; an approved switch "
|
|
767
|
+
"applies when the current turn ends. Use only when the conversation "
|
|
768
|
+
"clearly calls for a different posture (e.g. pure explanation -> ask, "
|
|
769
|
+
"implementation after planning -> code). Never required for normal work."
|
|
770
|
+
),
|
|
771
|
+
parameters={
|
|
772
|
+
"type": "object",
|
|
773
|
+
"properties": {
|
|
774
|
+
"persona": {
|
|
775
|
+
"type": "string",
|
|
776
|
+
"enum": ["code", "architect", "ask", "debug"],
|
|
777
|
+
},
|
|
778
|
+
"reason": {
|
|
779
|
+
"type": "string",
|
|
780
|
+
"description": "One short sentence shown to the user in the approval prompt.",
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
"required": ["persona", "reason"],
|
|
784
|
+
},
|
|
785
|
+
categories=("session",),
|
|
786
|
+
rich=RichToolMetadata(
|
|
787
|
+
display_name="Switch Persona",
|
|
788
|
+
reasoning_hint="The requested work fits a different persona posture.",
|
|
789
|
+
action_hint="Ask the user to approve a persona switch.",
|
|
790
|
+
fallback_hint="If declined, continue in the current persona without asking again.",
|
|
791
|
+
),
|
|
792
|
+
built_in_subagent_exposure="readonly",
|
|
793
|
+
optional=True,
|
|
794
|
+
optional_unavailable_reason="persona modes disabled or non-interactive runtime",
|
|
795
|
+
),
|
|
796
|
+
BuiltinToolMetadata(
|
|
797
|
+
name="fs_read",
|
|
798
|
+
description=(
|
|
799
|
+
"Read a UTF-8 text file under the working root. Prefer after symbol_search or "
|
|
800
|
+
"search_rg for exact file contents. Derived artifacts (lockfiles, minified or "
|
|
801
|
+
"generated output) return size + head sample unless allow_derived=true. Truncation "
|
|
802
|
+
"reports total, returned, and next line ranges."
|
|
803
|
+
),
|
|
804
|
+
parameters={
|
|
805
|
+
"type": "object",
|
|
806
|
+
"properties": {
|
|
807
|
+
"path": {"type": "string"},
|
|
808
|
+
"path_base": _path_base_property(
|
|
809
|
+
description=(
|
|
810
|
+
"Resolve a relative path from the live active_workdir (default) or from the "
|
|
811
|
+
"immutable workspace_root."
|
|
812
|
+
)
|
|
813
|
+
),
|
|
814
|
+
"max_bytes": {"type": "integer", "default": 12000},
|
|
815
|
+
"allow_derived": {
|
|
816
|
+
"type": "boolean",
|
|
817
|
+
"default": False,
|
|
818
|
+
"description": (
|
|
819
|
+
"Full content for derived artifacts; set only when the artifact "
|
|
820
|
+
"itself is the subject of the task."
|
|
821
|
+
),
|
|
822
|
+
},
|
|
823
|
+
"force": {
|
|
824
|
+
"type": "boolean",
|
|
825
|
+
"default": False,
|
|
826
|
+
"description": "Return content even if this unchanged range was read before.",
|
|
827
|
+
},
|
|
828
|
+
},
|
|
829
|
+
"required": ["path"],
|
|
830
|
+
},
|
|
831
|
+
categories=("read", "fs"),
|
|
832
|
+
rich=RichToolMetadata(
|
|
833
|
+
display_name="Read File",
|
|
834
|
+
reasoning_hint="Need exact file content before suggesting edits.",
|
|
835
|
+
action_hint="Read file text from the current workspace.",
|
|
836
|
+
fallback_hint="If read fails, adjust path or list files first.",
|
|
837
|
+
input_preview_formatter=_preview_single_path,
|
|
838
|
+
output_summary_formatter=_summary_fs_read,
|
|
839
|
+
),
|
|
840
|
+
built_in_subagent_exposure="readonly",
|
|
841
|
+
),
|
|
842
|
+
BuiltinToolMetadata(
|
|
843
|
+
name="fs_read_lines",
|
|
844
|
+
description=(
|
|
845
|
+
"Read a precise 1-indexed line range from a UTF-8 text file. Prefer a narrow "
|
|
846
|
+
"confirmed range; truncation reports the exact next range."
|
|
847
|
+
),
|
|
848
|
+
parameters={
|
|
849
|
+
"type": "object",
|
|
850
|
+
"properties": {
|
|
851
|
+
"path": {"type": "string"},
|
|
852
|
+
"path_base": _path_base_property(),
|
|
853
|
+
"start_line": {"type": "integer"},
|
|
854
|
+
"end_line": {"type": "integer"},
|
|
855
|
+
"max_lines": {"type": "integer", "default": 200},
|
|
856
|
+
"include_line_numbers": {"type": "boolean", "default": True},
|
|
857
|
+
"max_bytes": {
|
|
858
|
+
"type": "integer",
|
|
859
|
+
"default": 48000,
|
|
860
|
+
"description": "Byte ceiling for the returned range.",
|
|
861
|
+
},
|
|
862
|
+
"force": {
|
|
863
|
+
"type": "boolean",
|
|
864
|
+
"default": False,
|
|
865
|
+
"description": "Return content even if this unchanged range was read before.",
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
"required": ["path", "start_line"],
|
|
869
|
+
},
|
|
870
|
+
categories=("read", "fs"),
|
|
871
|
+
rich=RichToolMetadata(
|
|
872
|
+
display_name="Read File Lines",
|
|
873
|
+
reasoning_hint="Inspect a precise file range without rereading the whole file.",
|
|
874
|
+
action_hint="Read a narrow 1-indexed line window from the current workspace.",
|
|
875
|
+
fallback_hint="If the range is wrong, adjust start/end lines or fall back to fs_read.",
|
|
876
|
+
input_preview_formatter=_preview_fs_read_lines,
|
|
877
|
+
output_summary_formatter=_summary_fs_read_lines,
|
|
878
|
+
),
|
|
879
|
+
built_in_subagent_exposure="readonly",
|
|
880
|
+
),
|
|
881
|
+
BuiltinToolMetadata(
|
|
882
|
+
name="fs_edit",
|
|
883
|
+
description=(
|
|
884
|
+
"Apply deterministic edits to one UTF-8 text file. Prefer for localized edits to an "
|
|
885
|
+
"existing file. Prefer line-range edits after fs_read_lines; use exact-text ops when "
|
|
886
|
+
"matching known text."
|
|
887
|
+
),
|
|
888
|
+
parameters=_fs_edit_parameters(),
|
|
889
|
+
categories=("write", "fs"),
|
|
890
|
+
rich=RichToolMetadata(
|
|
891
|
+
display_name="Edit File",
|
|
892
|
+
reasoning_hint="Apply deterministic exact-text or line-range edits to one file.",
|
|
893
|
+
action_hint=(
|
|
894
|
+
"Edit a localized file region with line-range or exact-match operations and review "
|
|
895
|
+
"the diff preview."
|
|
896
|
+
),
|
|
897
|
+
fallback_hint=(
|
|
898
|
+
"If exact text is ambiguous or missing, re-read lines and use replace_lines with "
|
|
899
|
+
"expected_old; use git_apply_patch for broader multi-file patches."
|
|
900
|
+
),
|
|
901
|
+
input_preview_formatter=_preview_single_path,
|
|
902
|
+
output_summary_formatter=_summary_fs_edit,
|
|
903
|
+
),
|
|
904
|
+
),
|
|
905
|
+
BuiltinToolMetadata(
|
|
906
|
+
name="fs_move",
|
|
907
|
+
description="Move or rename one file under the working root. Prefer over shell commands for routine file moves.",
|
|
908
|
+
parameters={
|
|
909
|
+
"type": "object",
|
|
910
|
+
"properties": {
|
|
911
|
+
"source_path": {"type": "string"},
|
|
912
|
+
"source_path_base": _path_base_property(),
|
|
913
|
+
"destination_path": {"type": "string"},
|
|
914
|
+
"destination_path_base": _path_base_property(),
|
|
915
|
+
"overwrite": {"type": "boolean", "default": False},
|
|
916
|
+
},
|
|
917
|
+
"required": ["source_path", "destination_path"],
|
|
918
|
+
},
|
|
919
|
+
categories=("write", "fs"),
|
|
920
|
+
rich=RichToolMetadata(
|
|
921
|
+
display_name="Move File",
|
|
922
|
+
reasoning_hint="Rename or relocate one file without shell commands.",
|
|
923
|
+
action_hint=(
|
|
924
|
+
"Move a single file under the workspace root and confirm the source/destination preview."
|
|
925
|
+
),
|
|
926
|
+
fallback_hint=(
|
|
927
|
+
"If the destination exists or the path is wrong, adjust the target or enable overwrite explicitly."
|
|
928
|
+
),
|
|
929
|
+
input_preview_formatter=_preview_source_destination,
|
|
930
|
+
output_summary_formatter=_summary_fs_move,
|
|
931
|
+
),
|
|
932
|
+
),
|
|
933
|
+
BuiltinToolMetadata(
|
|
934
|
+
name="fs_copy",
|
|
935
|
+
description="Copy one file under the working root. Prefer over shell commands for routine file copies.",
|
|
936
|
+
parameters={
|
|
937
|
+
"type": "object",
|
|
938
|
+
"properties": {
|
|
939
|
+
"source_path": {"type": "string"},
|
|
940
|
+
"source_path_base": _path_base_property(),
|
|
941
|
+
"destination_path": {"type": "string"},
|
|
942
|
+
"destination_path_base": _path_base_property(),
|
|
943
|
+
"overwrite": {"type": "boolean", "default": False},
|
|
944
|
+
},
|
|
945
|
+
"required": ["source_path", "destination_path"],
|
|
946
|
+
},
|
|
947
|
+
categories=("write", "fs"),
|
|
948
|
+
rich=RichToolMetadata(
|
|
949
|
+
display_name="Copy File",
|
|
950
|
+
reasoning_hint="Duplicate one file without shell commands.",
|
|
951
|
+
action_hint=(
|
|
952
|
+
"Copy a single file under the workspace root and confirm the destination preview."
|
|
953
|
+
),
|
|
954
|
+
fallback_hint=(
|
|
955
|
+
"If the destination exists or the path is wrong, adjust the target or enable overwrite explicitly."
|
|
956
|
+
),
|
|
957
|
+
input_preview_formatter=_preview_source_destination,
|
|
958
|
+
output_summary_formatter=_summary_fs_copy,
|
|
959
|
+
),
|
|
960
|
+
),
|
|
961
|
+
BuiltinToolMetadata(
|
|
962
|
+
name="fs_delete",
|
|
963
|
+
description="Delete one file under the working root. Prefer over shell commands for routine file deletes.",
|
|
964
|
+
parameters={
|
|
965
|
+
"type": "object",
|
|
966
|
+
"properties": {
|
|
967
|
+
"path": {"type": "string"},
|
|
968
|
+
"path_base": _path_base_property(),
|
|
969
|
+
},
|
|
970
|
+
"required": ["path"],
|
|
971
|
+
},
|
|
972
|
+
categories=("write", "fs"),
|
|
973
|
+
rich=RichToolMetadata(
|
|
974
|
+
display_name="Delete File",
|
|
975
|
+
reasoning_hint="Remove one file without shell commands.",
|
|
976
|
+
action_hint=(
|
|
977
|
+
"Delete a single file under the workspace root and confirm the preview before continuing."
|
|
978
|
+
),
|
|
979
|
+
fallback_hint="If the path is wrong or protected, adjust the target instead of forcing the delete.",
|
|
980
|
+
input_preview_formatter=_preview_single_path,
|
|
981
|
+
output_summary_formatter=_summary_fs_delete,
|
|
982
|
+
),
|
|
983
|
+
),
|
|
984
|
+
BuiltinToolMetadata(
|
|
985
|
+
name="fs_write",
|
|
986
|
+
description="Write a UTF-8 text file under the working root. Prefer for new/generated files or full-file replacements.",
|
|
987
|
+
parameters={
|
|
988
|
+
"type": "object",
|
|
989
|
+
"properties": {
|
|
990
|
+
"path": {"type": "string"},
|
|
991
|
+
"path_base": _path_base_property(),
|
|
992
|
+
"content": {"type": "string"},
|
|
993
|
+
},
|
|
994
|
+
"required": ["path", "content"],
|
|
995
|
+
},
|
|
996
|
+
categories=("write", "fs"),
|
|
997
|
+
rich=RichToolMetadata(
|
|
998
|
+
display_name="Write File",
|
|
999
|
+
reasoning_hint="Apply a concrete code/content update.",
|
|
1000
|
+
action_hint="Write new file content and verify patch preview.",
|
|
1001
|
+
fallback_hint="If blocked, ask for approval or reduce scope.",
|
|
1002
|
+
input_preview_formatter=_preview_single_path,
|
|
1003
|
+
output_summary_formatter=_summary_fs_write,
|
|
1004
|
+
),
|
|
1005
|
+
),
|
|
1006
|
+
BuiltinToolMetadata(
|
|
1007
|
+
name="fs_mkdir",
|
|
1008
|
+
description="Create one directory under the working root.",
|
|
1009
|
+
parameters={
|
|
1010
|
+
"type": "object",
|
|
1011
|
+
"properties": {
|
|
1012
|
+
"path": {"type": "string"},
|
|
1013
|
+
"path_base": _path_base_property(),
|
|
1014
|
+
"parents": {"type": "boolean", "default": True},
|
|
1015
|
+
"exist_ok": {"type": "boolean", "default": True},
|
|
1016
|
+
},
|
|
1017
|
+
"required": ["path"],
|
|
1018
|
+
},
|
|
1019
|
+
categories=("write", "fs"),
|
|
1020
|
+
rich=RichToolMetadata(
|
|
1021
|
+
display_name="Create Directory",
|
|
1022
|
+
reasoning_hint="Create empty directories or explicit scaffolding without shell commands.",
|
|
1023
|
+
action_hint="Create a workspace-bounded directory path with optional parent creation.",
|
|
1024
|
+
fallback_hint=(
|
|
1025
|
+
"If the target collides with a file or the path is protected, adjust the path or use fs_write for files."
|
|
1026
|
+
),
|
|
1027
|
+
input_preview_formatter=_preview_single_path,
|
|
1028
|
+
output_summary_formatter=_summary_fs_mkdir,
|
|
1029
|
+
),
|
|
1030
|
+
),
|
|
1031
|
+
BuiltinToolMetadata(
|
|
1032
|
+
name="fs_list",
|
|
1033
|
+
description="List files under root_path (best-effort .gitignore support).",
|
|
1034
|
+
parameters={
|
|
1035
|
+
"type": "object",
|
|
1036
|
+
"properties": {
|
|
1037
|
+
"root_path": {"type": "string"},
|
|
1038
|
+
"path_base": _path_base_property(
|
|
1039
|
+
description=(
|
|
1040
|
+
"Resolve a relative root_path from the live active_workdir (default) or from "
|
|
1041
|
+
"the immutable workspace_root."
|
|
1042
|
+
)
|
|
1043
|
+
),
|
|
1044
|
+
"globs": {"type": "array", "items": {"type": "string"}},
|
|
1045
|
+
"ignore": {"type": "array", "items": {"type": "string"}},
|
|
1046
|
+
},
|
|
1047
|
+
"required": [],
|
|
1048
|
+
},
|
|
1049
|
+
categories=("read", "fs"),
|
|
1050
|
+
rich=RichToolMetadata(
|
|
1051
|
+
display_name="List Files",
|
|
1052
|
+
reasoning_hint="Discover relevant files for the task.",
|
|
1053
|
+
action_hint="List workspace paths with optional filters.",
|
|
1054
|
+
fallback_hint="If results are noisy, narrow globs and retry.",
|
|
1055
|
+
output_summary_formatter=_summary_fs_list,
|
|
1056
|
+
),
|
|
1057
|
+
built_in_subagent_exposure="readonly",
|
|
1058
|
+
),
|
|
1059
|
+
BuiltinToolMetadata(
|
|
1060
|
+
name="web_fetch",
|
|
1061
|
+
description=(
|
|
1062
|
+
"Fetch one specific known HTTP(S) URL with SSRF-style safety checks and return readable text. "
|
|
1063
|
+
"Prefer it only for a user-provided URL or one returned by web_search; the runtime rejects guessed "
|
|
1064
|
+
"URLs. Do not use it for discovery and do not guess or invent URLs."
|
|
1065
|
+
),
|
|
1066
|
+
parameters={
|
|
1067
|
+
"type": "object",
|
|
1068
|
+
"properties": {
|
|
1069
|
+
"url": {"type": "string"},
|
|
1070
|
+
"max_chars": {"type": "integer", "default": 20000, "minimum": 1, "maximum": 50000},
|
|
1071
|
+
},
|
|
1072
|
+
"required": ["url"],
|
|
1073
|
+
},
|
|
1074
|
+
categories=("read", "web"),
|
|
1075
|
+
rich=RichToolMetadata(
|
|
1076
|
+
display_name="Fetch Web Page",
|
|
1077
|
+
reasoning_hint="Read targeted external docs/spec pages without shelling out.",
|
|
1078
|
+
action_hint="Fetch one URL and inspect extracted readable text and metadata.",
|
|
1079
|
+
fallback_hint=(
|
|
1080
|
+
"If blocked or unsupported, use a different public URL or request manual input."
|
|
1081
|
+
),
|
|
1082
|
+
input_preview_formatter=_preview_web_fetch,
|
|
1083
|
+
output_summary_formatter=_summary_web_fetch,
|
|
1084
|
+
),
|
|
1085
|
+
built_in_subagent_exposure="hidden",
|
|
1086
|
+
),
|
|
1087
|
+
BuiltinToolMetadata(
|
|
1088
|
+
name="web_search",
|
|
1089
|
+
description=(
|
|
1090
|
+
"Search the public web and return grounded content with cited sources. Decide to use it "
|
|
1091
|
+
"whenever a reliable answer depends on unstable external facts, authoritative current "
|
|
1092
|
+
"sources, current high-stakes guidance, current product or service information, or "
|
|
1093
|
+
"requested internet research. Every result includes `retrieved_at` — the UTC wall-clock "
|
|
1094
|
+
"time the search executed; trust it over your training prior for what 'today'/'current' "
|
|
1095
|
+
"means. Use web_fetch only after you have a URL the user provided or web_search returned. "
|
|
1096
|
+
"`external_web_access=false` is only supported by the OpenAI Responses backend."
|
|
1097
|
+
),
|
|
1098
|
+
parameters={
|
|
1099
|
+
"type": "object",
|
|
1100
|
+
"properties": {
|
|
1101
|
+
"query": {"type": "string"},
|
|
1102
|
+
"allowed_domains": {"type": "array", "items": {"type": "string"}},
|
|
1103
|
+
"max_sources": {"type": "integer", "default": 8, "minimum": 1, "maximum": 20},
|
|
1104
|
+
"external_web_access": {"type": "boolean", "default": True},
|
|
1105
|
+
},
|
|
1106
|
+
"required": ["query"],
|
|
1107
|
+
},
|
|
1108
|
+
categories=("read", "web", "search"),
|
|
1109
|
+
rich=RichToolMetadata(
|
|
1110
|
+
display_name="Search Web",
|
|
1111
|
+
reasoning_hint="Discover the right external docs/page/source before fetching a specific URL.",
|
|
1112
|
+
action_hint="Run a bounded web search and return an answer with citations and source URLs.",
|
|
1113
|
+
fallback_hint=(
|
|
1114
|
+
"If unavailable, use a user-provided direct public URL with web_fetch or ask the user for a "
|
|
1115
|
+
"target page."
|
|
1116
|
+
),
|
|
1117
|
+
input_preview_formatter=_preview_web_search,
|
|
1118
|
+
output_summary_formatter=_summary_web_search,
|
|
1119
|
+
),
|
|
1120
|
+
built_in_subagent_exposure="hidden",
|
|
1121
|
+
),
|
|
1122
|
+
BuiltinToolMetadata(
|
|
1123
|
+
name="ide_task_list",
|
|
1124
|
+
description=(
|
|
1125
|
+
"List bounded workspace tasks from a capability-negotiated trusted VS Code host. "
|
|
1126
|
+
"Returned task ids are opaque and must be passed unchanged to ide_task_run."
|
|
1127
|
+
),
|
|
1128
|
+
parameters={
|
|
1129
|
+
"type": "object",
|
|
1130
|
+
"properties": {"task_type": {"type": "string"}},
|
|
1131
|
+
"required": [],
|
|
1132
|
+
},
|
|
1133
|
+
categories=("read", "ide", "task"),
|
|
1134
|
+
rich=RichToolMetadata(
|
|
1135
|
+
display_name="List IDE Tasks",
|
|
1136
|
+
reasoning_hint="Discover the repository's canonical VS Code tasks before invoking one.",
|
|
1137
|
+
action_hint="Ask the trusted IDE host for a bounded task inventory.",
|
|
1138
|
+
fallback_hint="If unavailable, inspect task configuration or use policy-checked shell tools.",
|
|
1139
|
+
),
|
|
1140
|
+
built_in_subagent_exposure="readonly",
|
|
1141
|
+
optional=True,
|
|
1142
|
+
optional_unavailable_reason="The trusted IDE host did not advertise tasks.list.",
|
|
1143
|
+
),
|
|
1144
|
+
BuiltinToolMetadata(
|
|
1145
|
+
name="ide_task_run",
|
|
1146
|
+
description=(
|
|
1147
|
+
"Start one VS Code workspace task by the opaque task id returned by ide_task_list. "
|
|
1148
|
+
"The trusted IDE host owns task resolution and execution."
|
|
1149
|
+
),
|
|
1150
|
+
parameters={
|
|
1151
|
+
"type": "object",
|
|
1152
|
+
"properties": {"task_id": {"type": "string"}},
|
|
1153
|
+
"required": ["task_id"],
|
|
1154
|
+
},
|
|
1155
|
+
categories=("write", "ide", "task"),
|
|
1156
|
+
rich=RichToolMetadata(
|
|
1157
|
+
display_name="Run IDE Task",
|
|
1158
|
+
reasoning_hint="Use the workspace's canonical task graph, providers, and problem matchers.",
|
|
1159
|
+
action_hint="Start one trusted-host task by opaque id.",
|
|
1160
|
+
fallback_hint="List tasks again if the id is stale, or use a policy-checked shell command.",
|
|
1161
|
+
),
|
|
1162
|
+
optional=True,
|
|
1163
|
+
optional_unavailable_reason="The trusted IDE host did not advertise tasks.run.",
|
|
1164
|
+
),
|
|
1165
|
+
BuiltinToolMetadata(
|
|
1166
|
+
name="ide_task_status",
|
|
1167
|
+
description=(
|
|
1168
|
+
"Read bounded status for VS Code task executions owned by this IDE session, optionally "
|
|
1169
|
+
"filtered by one opaque execution id."
|
|
1170
|
+
),
|
|
1171
|
+
parameters={
|
|
1172
|
+
"type": "object",
|
|
1173
|
+
"properties": {"execution_id": {"type": "string"}},
|
|
1174
|
+
"required": [],
|
|
1175
|
+
},
|
|
1176
|
+
categories=("read", "ide", "task"),
|
|
1177
|
+
rich=RichToolMetadata(
|
|
1178
|
+
display_name="Read IDE Task Status",
|
|
1179
|
+
reasoning_hint="Check whether a host-owned task is still running or has ended.",
|
|
1180
|
+
action_hint="Read bounded status for one or all session-owned task executions.",
|
|
1181
|
+
fallback_hint="Use VS Code's task controls if host status is unavailable.",
|
|
1182
|
+
),
|
|
1183
|
+
built_in_subagent_exposure="readonly",
|
|
1184
|
+
optional=True,
|
|
1185
|
+
optional_unavailable_reason="The trusted IDE host did not advertise tasks.status.",
|
|
1186
|
+
),
|
|
1187
|
+
BuiltinToolMetadata(
|
|
1188
|
+
name="ide_task_terminate",
|
|
1189
|
+
description=(
|
|
1190
|
+
"Terminate one VS Code task execution by the opaque execution id returned by "
|
|
1191
|
+
"ide_task_run."
|
|
1192
|
+
),
|
|
1193
|
+
parameters={
|
|
1194
|
+
"type": "object",
|
|
1195
|
+
"properties": {"execution_id": {"type": "string"}},
|
|
1196
|
+
"required": ["execution_id"],
|
|
1197
|
+
},
|
|
1198
|
+
categories=("write", "ide", "task"),
|
|
1199
|
+
rich=RichToolMetadata(
|
|
1200
|
+
display_name="Terminate IDE Task",
|
|
1201
|
+
reasoning_hint="Stop a host-owned task execution that is no longer needed.",
|
|
1202
|
+
action_hint="Ask the trusted IDE host to terminate one opaque execution id.",
|
|
1203
|
+
fallback_hint="List or inspect task state in VS Code if the execution id is stale.",
|
|
1204
|
+
),
|
|
1205
|
+
optional=True,
|
|
1206
|
+
optional_unavailable_reason="The trusted IDE host did not advertise tasks.terminate.",
|
|
1207
|
+
),
|
|
1208
|
+
BuiltinToolMetadata(
|
|
1209
|
+
name="ide_debug_list",
|
|
1210
|
+
description=(
|
|
1211
|
+
"List bounded launch configurations from a capability-negotiated trusted VS Code "
|
|
1212
|
+
"host. Configuration ids are opaque and workspace scoped."
|
|
1213
|
+
),
|
|
1214
|
+
parameters={"type": "object", "properties": {}, "required": []},
|
|
1215
|
+
categories=("read", "ide", "debug"),
|
|
1216
|
+
rich=RichToolMetadata(
|
|
1217
|
+
display_name="List IDE Debug Configurations",
|
|
1218
|
+
reasoning_hint="Discover repository-owned launch configurations before starting one.",
|
|
1219
|
+
action_hint="Ask the trusted IDE host for a bounded launch configuration inventory.",
|
|
1220
|
+
fallback_hint="If unavailable, inspect launch.json or use tests and shell verification.",
|
|
1221
|
+
),
|
|
1222
|
+
built_in_subagent_exposure="readonly",
|
|
1223
|
+
optional=True,
|
|
1224
|
+
optional_unavailable_reason="The trusted IDE host did not advertise debug.list.",
|
|
1225
|
+
),
|
|
1226
|
+
BuiltinToolMetadata(
|
|
1227
|
+
name="ide_debug_start",
|
|
1228
|
+
description=(
|
|
1229
|
+
"Start one VS Code debug configuration by the opaque id returned by ide_debug_list. "
|
|
1230
|
+
"The trusted IDE host resolves the actual launch configuration."
|
|
1231
|
+
),
|
|
1232
|
+
parameters={
|
|
1233
|
+
"type": "object",
|
|
1234
|
+
"properties": {"configuration_id": {"type": "string"}},
|
|
1235
|
+
"required": ["configuration_id"],
|
|
1236
|
+
},
|
|
1237
|
+
categories=("write", "ide", "debug"),
|
|
1238
|
+
rich=RichToolMetadata(
|
|
1239
|
+
display_name="Start IDE Debug Session",
|
|
1240
|
+
reasoning_hint="Launch a repository-owned debug configuration through VS Code.",
|
|
1241
|
+
action_hint="Start one trusted-host debug configuration by opaque id.",
|
|
1242
|
+
fallback_hint="List configurations again if the id is stale, or use tests and logs.",
|
|
1243
|
+
),
|
|
1244
|
+
optional=True,
|
|
1245
|
+
optional_unavailable_reason="The trusted IDE host did not advertise debug.start.",
|
|
1246
|
+
),
|
|
1247
|
+
BuiltinToolMetadata(
|
|
1248
|
+
name="ide_debug_stop",
|
|
1249
|
+
description="Stop one VS Code debug session by the opaque id returned by ide_debug_start.",
|
|
1250
|
+
parameters={
|
|
1251
|
+
"type": "object",
|
|
1252
|
+
"properties": {"debug_session_id": {"type": "string"}},
|
|
1253
|
+
"required": ["debug_session_id"],
|
|
1254
|
+
},
|
|
1255
|
+
categories=("write", "ide", "debug"),
|
|
1256
|
+
rich=RichToolMetadata(
|
|
1257
|
+
display_name="Stop IDE Debug Session",
|
|
1258
|
+
reasoning_hint="Stop a host-owned debug session that is no longer needed.",
|
|
1259
|
+
action_hint="Ask the trusted IDE host to stop one opaque debug session id.",
|
|
1260
|
+
fallback_hint="Inspect VS Code's debug controls if the session id is stale.",
|
|
1261
|
+
),
|
|
1262
|
+
optional=True,
|
|
1263
|
+
optional_unavailable_reason="The trusted IDE host did not advertise debug.stop.",
|
|
1264
|
+
),
|
|
1265
|
+
BuiltinToolMetadata(
|
|
1266
|
+
name="ide_debug_status",
|
|
1267
|
+
description=(
|
|
1268
|
+
"Read bounded VS Code debug-session status, optionally filtered by one opaque session "
|
|
1269
|
+
"id. The trusted IDE host owns session discovery."
|
|
1270
|
+
),
|
|
1271
|
+
parameters={
|
|
1272
|
+
"type": "object",
|
|
1273
|
+
"properties": {"debug_session_id": {"type": "string"}},
|
|
1274
|
+
"required": [],
|
|
1275
|
+
},
|
|
1276
|
+
categories=("read", "ide", "debug"),
|
|
1277
|
+
rich=RichToolMetadata(
|
|
1278
|
+
display_name="Read IDE Debug Status",
|
|
1279
|
+
reasoning_hint="Check whether a trusted-host debug session is still running.",
|
|
1280
|
+
action_hint="Read bounded status for one or all host-owned debug sessions.",
|
|
1281
|
+
fallback_hint="Use VS Code's debug controls if host status is unavailable.",
|
|
1282
|
+
),
|
|
1283
|
+
built_in_subagent_exposure="readonly",
|
|
1284
|
+
optional=True,
|
|
1285
|
+
optional_unavailable_reason="The trusted IDE host did not advertise debug.status.",
|
|
1286
|
+
),
|
|
1287
|
+
BuiltinToolMetadata(
|
|
1288
|
+
name="browser_start",
|
|
1289
|
+
description=(
|
|
1290
|
+
"Start one IDE-owned managed Chromium session, optionally at a public or "
|
|
1291
|
+
"session-owned preview URL. Other local/private destinations remain blocked."
|
|
1292
|
+
),
|
|
1293
|
+
parameters={
|
|
1294
|
+
"type": "object",
|
|
1295
|
+
"properties": {
|
|
1296
|
+
"url": {"type": "string"},
|
|
1297
|
+
"timeout": {"type": "number", "minimum": 0.05, "maximum": 300},
|
|
1298
|
+
},
|
|
1299
|
+
},
|
|
1300
|
+
categories=("write", "browser", "web"),
|
|
1301
|
+
rich=RichToolMetadata(
|
|
1302
|
+
display_name="Start Browser",
|
|
1303
|
+
reasoning_hint="Open an isolated managed browser before interactive page inspection.",
|
|
1304
|
+
action_hint="Start an approval-gated, IDE-owned Chromium session.",
|
|
1305
|
+
fallback_hint="If approval or launch fails, use web_search/web_fetch or ask the user.",
|
|
1306
|
+
),
|
|
1307
|
+
optional=True,
|
|
1308
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1309
|
+
built_in_subagent_exposure="hidden",
|
|
1310
|
+
),
|
|
1311
|
+
BuiltinToolMetadata(
|
|
1312
|
+
name="browser_navigate",
|
|
1313
|
+
description=(
|
|
1314
|
+
"Navigate an owned browser to a public URL or a live session-owned preview origin. "
|
|
1315
|
+
"Redirects and subresources use the same policy."
|
|
1316
|
+
),
|
|
1317
|
+
parameters={
|
|
1318
|
+
"type": "object",
|
|
1319
|
+
"properties": {
|
|
1320
|
+
"session_id": {"type": "string"},
|
|
1321
|
+
"url": {"type": "string"},
|
|
1322
|
+
"timeout": {"type": "number", "minimum": 0.05, "maximum": 300},
|
|
1323
|
+
},
|
|
1324
|
+
"required": ["session_id", "url"],
|
|
1325
|
+
},
|
|
1326
|
+
categories=("write", "browser", "web", "network"),
|
|
1327
|
+
rich=RichToolMetadata(
|
|
1328
|
+
display_name="Navigate Browser",
|
|
1329
|
+
reasoning_hint="Open a public page in the isolated managed browser.",
|
|
1330
|
+
action_hint="Navigate after explicit host approval and enforce the URL policy.",
|
|
1331
|
+
fallback_hint="If blocked, use a public URL or web_fetch instead.",
|
|
1332
|
+
),
|
|
1333
|
+
optional=True,
|
|
1334
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1335
|
+
built_in_subagent_exposure="hidden",
|
|
1336
|
+
),
|
|
1337
|
+
BuiltinToolMetadata(
|
|
1338
|
+
name="browser_snapshot",
|
|
1339
|
+
description=(
|
|
1340
|
+
"Read a bounded semantic, accessibility, DOM, or text snapshot from an owned "
|
|
1341
|
+
"managed-browser session."
|
|
1342
|
+
),
|
|
1343
|
+
parameters={
|
|
1344
|
+
"type": "object",
|
|
1345
|
+
"properties": {
|
|
1346
|
+
"session_id": {"type": "string"},
|
|
1347
|
+
"kind": {
|
|
1348
|
+
"type": "string",
|
|
1349
|
+
"enum": ["semantic", "accessibility", "dom", "text"],
|
|
1350
|
+
"default": "semantic",
|
|
1351
|
+
},
|
|
1352
|
+
"timeout": {"type": "number", "minimum": 0.05, "maximum": 300},
|
|
1353
|
+
},
|
|
1354
|
+
"required": ["session_id"],
|
|
1355
|
+
},
|
|
1356
|
+
categories=("read", "browser", "web"),
|
|
1357
|
+
rich=RichToolMetadata(
|
|
1358
|
+
display_name="Snapshot Browser",
|
|
1359
|
+
reasoning_hint="Inspect the current page structure or text before interacting.",
|
|
1360
|
+
action_hint="Read a bounded browser snapshot.",
|
|
1361
|
+
fallback_hint="Try a text snapshot or browser_screenshot if structure is unavailable.",
|
|
1362
|
+
),
|
|
1363
|
+
optional=True,
|
|
1364
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1365
|
+
built_in_subagent_exposure="readonly",
|
|
1366
|
+
),
|
|
1367
|
+
BuiltinToolMetadata(
|
|
1368
|
+
name="browser_screenshot",
|
|
1369
|
+
description=(
|
|
1370
|
+
"Capture a bounded PNG screenshot as an owner-scoped artifact. Returns only an "
|
|
1371
|
+
"artifact id and metadata, never a filesystem path."
|
|
1372
|
+
),
|
|
1373
|
+
parameters={
|
|
1374
|
+
"type": "object",
|
|
1375
|
+
"properties": {
|
|
1376
|
+
"session_id": {"type": "string"},
|
|
1377
|
+
"full_page": {"type": "boolean", "default": False},
|
|
1378
|
+
"timeout": {"type": "number", "minimum": 0.05, "maximum": 300},
|
|
1379
|
+
},
|
|
1380
|
+
"required": ["session_id"],
|
|
1381
|
+
},
|
|
1382
|
+
categories=("read", "browser", "web", "artifact"),
|
|
1383
|
+
rich=RichToolMetadata(
|
|
1384
|
+
display_name="Screenshot Browser",
|
|
1385
|
+
reasoning_hint="Capture visual page evidence that snapshots cannot represent.",
|
|
1386
|
+
action_hint="Create a private screenshot artifact and return its opaque id.",
|
|
1387
|
+
fallback_hint="Use browser_snapshot when visual evidence is unnecessary.",
|
|
1388
|
+
),
|
|
1389
|
+
optional=True,
|
|
1390
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1391
|
+
built_in_subagent_exposure="readonly",
|
|
1392
|
+
),
|
|
1393
|
+
BuiltinToolMetadata(
|
|
1394
|
+
name="browser_artifact_read",
|
|
1395
|
+
description=(
|
|
1396
|
+
"Read one bounded base64 chunk of an owner-scoped browser screenshot artifact by "
|
|
1397
|
+
"opaque artifact id. Caller-selected paths are not accepted."
|
|
1398
|
+
),
|
|
1399
|
+
parameters={
|
|
1400
|
+
"type": "object",
|
|
1401
|
+
"properties": {
|
|
1402
|
+
"session_id": {"type": "string"},
|
|
1403
|
+
"artifact_id": {"type": "string"},
|
|
1404
|
+
"offset": {"type": "integer", "minimum": 0, "default": 0},
|
|
1405
|
+
"max_bytes": {
|
|
1406
|
+
"type": "integer",
|
|
1407
|
+
"minimum": 1,
|
|
1408
|
+
"maximum": 1048576,
|
|
1409
|
+
"default": 262144,
|
|
1410
|
+
},
|
|
1411
|
+
},
|
|
1412
|
+
"required": ["session_id", "artifact_id"],
|
|
1413
|
+
},
|
|
1414
|
+
categories=("read", "browser", "artifact"),
|
|
1415
|
+
rich=RichToolMetadata(
|
|
1416
|
+
display_name="Read Browser Artifact",
|
|
1417
|
+
reasoning_hint="Read a bounded screenshot chunk using its opaque artifact id.",
|
|
1418
|
+
action_hint="Return one base64 screenshot chunk without exposing its path.",
|
|
1419
|
+
fallback_hint="Use a smaller chunk or the next_offset returned by the prior call.",
|
|
1420
|
+
),
|
|
1421
|
+
optional=True,
|
|
1422
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1423
|
+
built_in_subagent_exposure="readonly",
|
|
1424
|
+
),
|
|
1425
|
+
BuiltinToolMetadata(
|
|
1426
|
+
name="browser_diagnostics",
|
|
1427
|
+
description=(
|
|
1428
|
+
"Read bounded, redacted console and network diagnostic events from an owned "
|
|
1429
|
+
"managed-browser session."
|
|
1430
|
+
),
|
|
1431
|
+
parameters={
|
|
1432
|
+
"type": "object",
|
|
1433
|
+
"properties": {
|
|
1434
|
+
"session_id": {"type": "string"},
|
|
1435
|
+
"max_events": {"type": "integer", "minimum": 1, "maximum": 500},
|
|
1436
|
+
"timeout": {"type": "number", "minimum": 0.05, "maximum": 300},
|
|
1437
|
+
},
|
|
1438
|
+
"required": ["session_id"],
|
|
1439
|
+
},
|
|
1440
|
+
categories=("read", "browser", "web", "diagnostics"),
|
|
1441
|
+
rich=RichToolMetadata(
|
|
1442
|
+
display_name="Read Browser Diagnostics",
|
|
1443
|
+
reasoning_hint="Inspect bounded console and network failures after reproducing a bug.",
|
|
1444
|
+
action_hint="Read redacted browser diagnostics.",
|
|
1445
|
+
fallback_hint="Reproduce the issue, then request a smaller diagnostic window.",
|
|
1446
|
+
),
|
|
1447
|
+
optional=True,
|
|
1448
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1449
|
+
built_in_subagent_exposure="readonly",
|
|
1450
|
+
),
|
|
1451
|
+
BuiltinToolMetadata(
|
|
1452
|
+
name="browser_click",
|
|
1453
|
+
description="Click one CSS selector in an owned managed-browser session.",
|
|
1454
|
+
parameters={
|
|
1455
|
+
"type": "object",
|
|
1456
|
+
"properties": {
|
|
1457
|
+
"session_id": {"type": "string"},
|
|
1458
|
+
"selector": {"type": "string", "maxLength": 2000},
|
|
1459
|
+
"timeout": {"type": "number", "minimum": 0.05, "maximum": 300},
|
|
1460
|
+
},
|
|
1461
|
+
"required": ["session_id", "selector"],
|
|
1462
|
+
},
|
|
1463
|
+
categories=("write", "browser", "web"),
|
|
1464
|
+
rich=RichToolMetadata(
|
|
1465
|
+
display_name="Click Browser Element",
|
|
1466
|
+
reasoning_hint="Interact with a confirmed page element after inspecting the page.",
|
|
1467
|
+
action_hint="Click a selector after explicit host approval.",
|
|
1468
|
+
fallback_hint="Refresh the snapshot and use a more precise selector.",
|
|
1469
|
+
),
|
|
1470
|
+
optional=True,
|
|
1471
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1472
|
+
built_in_subagent_exposure="hidden",
|
|
1473
|
+
),
|
|
1474
|
+
BuiltinToolMetadata(
|
|
1475
|
+
name="browser_type",
|
|
1476
|
+
description=(
|
|
1477
|
+
"Type text into one CSS selector in an owned managed-browser session. Input text is "
|
|
1478
|
+
"never echoed in the result or approval preview."
|
|
1479
|
+
),
|
|
1480
|
+
parameters={
|
|
1481
|
+
"type": "object",
|
|
1482
|
+
"properties": {
|
|
1483
|
+
"session_id": {"type": "string"},
|
|
1484
|
+
"selector": {"type": "string", "maxLength": 2000},
|
|
1485
|
+
"text": {"type": "string", "maxLength": 100000},
|
|
1486
|
+
"replace": {"type": "boolean", "default": True},
|
|
1487
|
+
"timeout": {"type": "number", "minimum": 0.05, "maximum": 300},
|
|
1488
|
+
},
|
|
1489
|
+
"required": ["session_id", "selector", "text"],
|
|
1490
|
+
},
|
|
1491
|
+
categories=("write", "browser", "web"),
|
|
1492
|
+
rich=RichToolMetadata(
|
|
1493
|
+
display_name="Type in Browser",
|
|
1494
|
+
reasoning_hint="Enter user-approved text into a confirmed page field.",
|
|
1495
|
+
action_hint="Type without exposing the text in tool summaries or approval previews.",
|
|
1496
|
+
fallback_hint="Refresh the snapshot and confirm the selector before retrying.",
|
|
1497
|
+
),
|
|
1498
|
+
optional=True,
|
|
1499
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1500
|
+
built_in_subagent_exposure="hidden",
|
|
1501
|
+
),
|
|
1502
|
+
BuiltinToolMetadata(
|
|
1503
|
+
name="browser_status",
|
|
1504
|
+
description="Read the bounded public status of one owned managed-browser session.",
|
|
1505
|
+
parameters={
|
|
1506
|
+
"type": "object",
|
|
1507
|
+
"properties": {"session_id": {"type": "string"}},
|
|
1508
|
+
"required": ["session_id"],
|
|
1509
|
+
},
|
|
1510
|
+
categories=("read", "browser"),
|
|
1511
|
+
rich=RichToolMetadata(
|
|
1512
|
+
display_name="Browser Status",
|
|
1513
|
+
reasoning_hint="Check whether a managed browser session is active and where it is.",
|
|
1514
|
+
action_hint="Read browser status.",
|
|
1515
|
+
fallback_hint="List owned sessions if the id is unknown.",
|
|
1516
|
+
),
|
|
1517
|
+
optional=True,
|
|
1518
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1519
|
+
built_in_subagent_exposure="readonly",
|
|
1520
|
+
),
|
|
1521
|
+
BuiltinToolMetadata(
|
|
1522
|
+
name="browser_list",
|
|
1523
|
+
description="List bounded public statuses for managed-browser sessions owned by this IDE task.",
|
|
1524
|
+
parameters={"type": "object", "properties": {}},
|
|
1525
|
+
categories=("read", "browser"),
|
|
1526
|
+
rich=RichToolMetadata(
|
|
1527
|
+
display_name="List Browsers",
|
|
1528
|
+
reasoning_hint="Discover managed browser sessions owned by this IDE task.",
|
|
1529
|
+
action_hint="List owned browser sessions.",
|
|
1530
|
+
fallback_hint="Start a browser if no owned session exists.",
|
|
1531
|
+
),
|
|
1532
|
+
optional=True,
|
|
1533
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1534
|
+
built_in_subagent_exposure="readonly",
|
|
1535
|
+
),
|
|
1536
|
+
BuiltinToolMetadata(
|
|
1537
|
+
name="browser_close",
|
|
1538
|
+
description=(
|
|
1539
|
+
"Close one owned managed-browser session and its exact process group. Artifacts are "
|
|
1540
|
+
"retained unless the IDE lifecycle removes them."
|
|
1541
|
+
),
|
|
1542
|
+
parameters={
|
|
1543
|
+
"type": "object",
|
|
1544
|
+
"properties": {"session_id": {"type": "string"}},
|
|
1545
|
+
"required": ["session_id"],
|
|
1546
|
+
},
|
|
1547
|
+
categories=("write", "browser"),
|
|
1548
|
+
rich=RichToolMetadata(
|
|
1549
|
+
display_name="Close Browser",
|
|
1550
|
+
reasoning_hint="Release an owned managed browser when it is no longer needed.",
|
|
1551
|
+
action_hint="Close the exact owned browser process group after approval.",
|
|
1552
|
+
fallback_hint="Check browser_status if the session may already be closed.",
|
|
1553
|
+
),
|
|
1554
|
+
optional=True,
|
|
1555
|
+
optional_unavailable_reason="Managed browser service is not attached to this IDE session.",
|
|
1556
|
+
built_in_subagent_exposure="hidden",
|
|
1557
|
+
),
|
|
1558
|
+
BuiltinToolMetadata(
|
|
1559
|
+
name="symbol_search",
|
|
1560
|
+
description=(
|
|
1561
|
+
"Search Python (AST), JavaScript/TypeScript (heuristic), and Java (heuristic) symbols "
|
|
1562
|
+
"(functions, classes, methods, constants) under the working root. Prefer this before broad regex search when locating definitions."
|
|
1563
|
+
),
|
|
1564
|
+
parameters={
|
|
1565
|
+
"type": "object",
|
|
1566
|
+
"properties": {
|
|
1567
|
+
"query": {"type": "string"},
|
|
1568
|
+
"kind": {
|
|
1569
|
+
"type": "string",
|
|
1570
|
+
"enum": ["function", "class", "method", "constant"],
|
|
1571
|
+
},
|
|
1572
|
+
"root_path": {"type": "string"},
|
|
1573
|
+
"path_base": _path_base_property(),
|
|
1574
|
+
"globs": {"type": "array", "items": {"type": "string"}},
|
|
1575
|
+
"max_results": {"type": "integer", "default": 100},
|
|
1576
|
+
"exact": {"type": "boolean", "default": False},
|
|
1577
|
+
"include_details": {"type": "boolean", "default": False},
|
|
1578
|
+
"include_snippet": {"type": "boolean", "default": False},
|
|
1579
|
+
"include_references": {"type": "boolean", "default": False},
|
|
1580
|
+
},
|
|
1581
|
+
"required": ["query"],
|
|
1582
|
+
},
|
|
1583
|
+
categories=("read", "search", "symbol"),
|
|
1584
|
+
rich=RichToolMetadata(
|
|
1585
|
+
display_name="Symbol Search",
|
|
1586
|
+
reasoning_hint="Navigate Python or JS/TS definitions before broad regex search.",
|
|
1587
|
+
action_hint=(
|
|
1588
|
+
"Search parsed Python symbols plus pragmatic JS/TS symbols (class/function/method/constant)."
|
|
1589
|
+
),
|
|
1590
|
+
fallback_hint="If results are sparse, relax exact/kind filters or fall back to search_rg.",
|
|
1591
|
+
input_preview_formatter=_preview_symbol_search,
|
|
1592
|
+
output_summary_formatter=_summary_symbol_search,
|
|
1593
|
+
),
|
|
1594
|
+
built_in_subagent_exposure="readonly",
|
|
1595
|
+
),
|
|
1596
|
+
BuiltinToolMetadata(
|
|
1597
|
+
name="test_discover",
|
|
1598
|
+
description=(
|
|
1599
|
+
"Find likely tests and focused test commands for changed files, symbols, or a "
|
|
1600
|
+
"verification failure summary. Use for repair targeting before broad final verification."
|
|
1601
|
+
),
|
|
1602
|
+
parameters={
|
|
1603
|
+
"type": "object",
|
|
1604
|
+
"properties": {
|
|
1605
|
+
"paths": {"type": "array", "items": {"type": "string"}},
|
|
1606
|
+
"symbols": {"type": "array", "items": {"type": "string"}},
|
|
1607
|
+
"changed_only": {"type": "boolean", "default": False},
|
|
1608
|
+
"include_commands": {"type": "boolean", "default": True},
|
|
1609
|
+
"max_results": {"type": "integer", "default": 20, "minimum": 1, "maximum": 100},
|
|
1610
|
+
"failure_summary": {"type": "object"},
|
|
1611
|
+
},
|
|
1612
|
+
},
|
|
1613
|
+
categories=("read", "search", "verification"),
|
|
1614
|
+
rich=RichToolMetadata(
|
|
1615
|
+
display_name="Discover Tests",
|
|
1616
|
+
reasoning_hint="Map changed files or verification failures to likely focused tests.",
|
|
1617
|
+
action_hint="Suggest likely test files and candidate targeted commands without running them.",
|
|
1618
|
+
fallback_hint=(
|
|
1619
|
+
"If no focused test is found, use broad_commands or the configured verify_run contract."
|
|
1620
|
+
),
|
|
1621
|
+
output_summary_formatter=_summary_test_discover,
|
|
1622
|
+
),
|
|
1623
|
+
built_in_subagent_exposure="readonly",
|
|
1624
|
+
),
|
|
1625
|
+
BuiltinToolMetadata(
|
|
1626
|
+
name="repo_map",
|
|
1627
|
+
description=(
|
|
1628
|
+
"Build a compact heuristic map of files, imports, symbols, likely tests, and "
|
|
1629
|
+
"repo-native commands related to paths or symbols. Use before broad exploration in unfamiliar repos."
|
|
1630
|
+
),
|
|
1631
|
+
parameters={
|
|
1632
|
+
"type": "object",
|
|
1633
|
+
"properties": {
|
|
1634
|
+
"paths": {"type": "array", "items": {"type": "string"}},
|
|
1635
|
+
"symbols": {"type": "array", "items": {"type": "string"}},
|
|
1636
|
+
"include_tests": {"type": "boolean", "default": True},
|
|
1637
|
+
"include_imports": {"type": "boolean", "default": True},
|
|
1638
|
+
"include_references": {"type": "boolean", "default": False},
|
|
1639
|
+
"depth": {"type": "integer", "default": 2, "minimum": 0, "maximum": 4},
|
|
1640
|
+
"max_items": {"type": "integer", "default": 80, "minimum": 1, "maximum": 200},
|
|
1641
|
+
},
|
|
1642
|
+
},
|
|
1643
|
+
categories=("read", "search", "symbol", "verification"),
|
|
1644
|
+
rich=RichToolMetadata(
|
|
1645
|
+
display_name="Map Repo",
|
|
1646
|
+
reasoning_hint="Orient around changed paths/symbols before scattered file reads.",
|
|
1647
|
+
action_hint="Return related implementation files, imports, tests, and candidate commands.",
|
|
1648
|
+
fallback_hint="If the map is sparse, use symbol_search, search_rg, or test_discover directly.",
|
|
1649
|
+
output_summary_formatter=_summary_repo_map,
|
|
1650
|
+
),
|
|
1651
|
+
built_in_subagent_exposure="readonly",
|
|
1652
|
+
),
|
|
1653
|
+
BuiltinToolMetadata(
|
|
1654
|
+
name="search_rg",
|
|
1655
|
+
description=(
|
|
1656
|
+
"Search for a regex or literal pattern under root_path using ripgrep when available. "
|
|
1657
|
+
"Prefer this for fast text/code lookup before reading or patching files. "
|
|
1658
|
+
"Use context options for edit planning."
|
|
1659
|
+
),
|
|
1660
|
+
parameters={
|
|
1661
|
+
"type": "object",
|
|
1662
|
+
"properties": {
|
|
1663
|
+
"pattern": {"type": "string"},
|
|
1664
|
+
"root_path": {"type": "string"},
|
|
1665
|
+
"path_base": _path_base_property(),
|
|
1666
|
+
"globs": {"type": "array", "items": {"type": "string"}},
|
|
1667
|
+
"max_results": {"type": "integer", "default": 200, "minimum": 1, "maximum": 500},
|
|
1668
|
+
"before_context": {"type": "integer", "default": 0, "minimum": 0, "maximum": 5},
|
|
1669
|
+
"after_context": {"type": "integer", "default": 0, "minimum": 0, "maximum": 5},
|
|
1670
|
+
"literal": {"type": "boolean", "default": False},
|
|
1671
|
+
"case_sensitive": {"type": "boolean", "default": True},
|
|
1672
|
+
"include_hidden": {"type": "boolean", "default": False},
|
|
1673
|
+
},
|
|
1674
|
+
"required": ["pattern"],
|
|
1675
|
+
},
|
|
1676
|
+
categories=("read", "search"),
|
|
1677
|
+
rich=RichToolMetadata(
|
|
1678
|
+
display_name="Search Workspace",
|
|
1679
|
+
reasoning_hint="Locate exact code/text matches fast with optional surrounding context.",
|
|
1680
|
+
action_hint="Run bounded text search and return matching lines plus requested context.",
|
|
1681
|
+
fallback_hint="If no matches, broaden pattern and search scope.",
|
|
1682
|
+
input_preview_formatter=_preview_pattern,
|
|
1683
|
+
output_summary_formatter=_summary_search_rg,
|
|
1684
|
+
),
|
|
1685
|
+
built_in_subagent_exposure="readonly",
|
|
1686
|
+
),
|
|
1687
|
+
BuiltinToolMetadata(
|
|
1688
|
+
name="session_artifact_read",
|
|
1689
|
+
description=(
|
|
1690
|
+
"Read a bounded, redacted artifact from the current session using an exact "
|
|
1691
|
+
"session_artifacts/... locator returned by another tool. This accepts a locator, "
|
|
1692
|
+
"not a filesystem path."
|
|
1693
|
+
),
|
|
1694
|
+
parameters={
|
|
1695
|
+
"type": "object",
|
|
1696
|
+
"properties": {
|
|
1697
|
+
"locator": {"type": "string"},
|
|
1698
|
+
"offset": {"type": "integer", "default": 0, "minimum": 0},
|
|
1699
|
+
"max_bytes": {
|
|
1700
|
+
"type": "integer",
|
|
1701
|
+
"default": 65536,
|
|
1702
|
+
"minimum": 1,
|
|
1703
|
+
"maximum": 1048576,
|
|
1704
|
+
},
|
|
1705
|
+
},
|
|
1706
|
+
"required": ["locator"],
|
|
1707
|
+
},
|
|
1708
|
+
categories=("read", "history", "artifact"),
|
|
1709
|
+
rich=RichToolMetadata(
|
|
1710
|
+
display_name="Read Session Artifact",
|
|
1711
|
+
reasoning_hint="Resolve an advertised current-session artifact locator directly.",
|
|
1712
|
+
action_hint="Read a bounded, redacted page from the current session artifact store.",
|
|
1713
|
+
fallback_hint="Use the exact locator returned by the producing tool.",
|
|
1714
|
+
),
|
|
1715
|
+
built_in_subagent_exposure="readonly",
|
|
1716
|
+
),
|
|
1717
|
+
BuiltinToolMetadata(
|
|
1718
|
+
name="history_search",
|
|
1719
|
+
description=(
|
|
1720
|
+
"Search current session artifacts (history chunks, tool outputs, and memory files) for a regex pattern."
|
|
1721
|
+
),
|
|
1722
|
+
parameters={
|
|
1723
|
+
"type": "object",
|
|
1724
|
+
"properties": {
|
|
1725
|
+
"pattern": {"type": "string"},
|
|
1726
|
+
"max_results": {"type": "integer", "default": 50},
|
|
1727
|
+
"max_file_bytes": {"type": "integer", "default": 200000},
|
|
1728
|
+
"include_history": {"type": "boolean", "default": True},
|
|
1729
|
+
"include_tool_outputs": {"type": "boolean", "default": True},
|
|
1730
|
+
"include_memory": {"type": "boolean", "default": True},
|
|
1731
|
+
},
|
|
1732
|
+
"required": ["pattern"],
|
|
1733
|
+
},
|
|
1734
|
+
categories=("read", "history"),
|
|
1735
|
+
rich=RichToolMetadata(
|
|
1736
|
+
display_name="Search Session History",
|
|
1737
|
+
reasoning_hint="Inspect current session artifacts without rereading every history file.",
|
|
1738
|
+
action_hint="Search stored history chunks, tool outputs, and memory summaries for a pattern.",
|
|
1739
|
+
fallback_hint="If results are sparse, widen the regex or include more artifact types.",
|
|
1740
|
+
input_preview_formatter=_preview_pattern,
|
|
1741
|
+
output_summary_formatter=_summary_history_search,
|
|
1742
|
+
),
|
|
1743
|
+
built_in_subagent_exposure="readonly",
|
|
1744
|
+
),
|
|
1745
|
+
BuiltinToolMetadata(
|
|
1746
|
+
name="knowledge_capture_json",
|
|
1747
|
+
description=(
|
|
1748
|
+
"Optional host-observed structured knowledge capture marker. This is not a "
|
|
1749
|
+
"callable runtime tool; the host parses a final assistant fenced block when present."
|
|
1750
|
+
),
|
|
1751
|
+
parameters={
|
|
1752
|
+
"type": "object",
|
|
1753
|
+
"properties": {
|
|
1754
|
+
"schema_version": {"type": "integer"},
|
|
1755
|
+
"facts": {"type": "array"},
|
|
1756
|
+
"decisions": {"type": "array"},
|
|
1757
|
+
"open_questions": {"type": "array"},
|
|
1758
|
+
},
|
|
1759
|
+
"required": [],
|
|
1760
|
+
},
|
|
1761
|
+
categories=("knowledge", "optional"),
|
|
1762
|
+
rich=RichToolMetadata(
|
|
1763
|
+
display_name="Knowledge Capture",
|
|
1764
|
+
reasoning_hint=(
|
|
1765
|
+
"Record reusable repo knowledge only through the final assistant fenced block."
|
|
1766
|
+
),
|
|
1767
|
+
action_hint=(
|
|
1768
|
+
"Do not call this as a runtime tool; append the bounded fenced JSON block when useful."
|
|
1769
|
+
),
|
|
1770
|
+
fallback_hint=(
|
|
1771
|
+
"If unavailable, continue with the normal final response; missing capture is non-fatal."
|
|
1772
|
+
),
|
|
1773
|
+
output_summary_formatter=lambda parsed: (
|
|
1774
|
+
"Optional knowledge capture tool unavailable."
|
|
1775
|
+
if is_tool_unavailable_result(parsed)
|
|
1776
|
+
else "Knowledge capture metadata result."
|
|
1777
|
+
),
|
|
1778
|
+
),
|
|
1779
|
+
optional=True,
|
|
1780
|
+
optional_unavailable_reason=(
|
|
1781
|
+
"not registered in active tool registry; knowledge capture is a final assistant "
|
|
1782
|
+
"fenced block parsed by host"
|
|
1783
|
+
),
|
|
1784
|
+
),
|
|
1785
|
+
BuiltinToolMetadata(
|
|
1786
|
+
name="skill_read",
|
|
1787
|
+
description=(
|
|
1788
|
+
"Read a discovered skill bundle entrypoint or one specific file within that skill bundle. "
|
|
1789
|
+
"Use this to inspect SKILL.md or targeted references/scripts/assets before applying a skill."
|
|
1790
|
+
),
|
|
1791
|
+
parameters={
|
|
1792
|
+
"type": "object",
|
|
1793
|
+
"properties": {
|
|
1794
|
+
"name": {"type": "string"},
|
|
1795
|
+
"path": {"type": "string"},
|
|
1796
|
+
},
|
|
1797
|
+
"required": ["name"],
|
|
1798
|
+
},
|
|
1799
|
+
categories=("read", "skills"),
|
|
1800
|
+
rich=RichToolMetadata(
|
|
1801
|
+
display_name="Read Skill",
|
|
1802
|
+
reasoning_hint="Inspect a discovered skill bundle before relying on its instructions.",
|
|
1803
|
+
action_hint="Read SKILL.md or one bundle file by name from the discovered skills registry.",
|
|
1804
|
+
fallback_hint="If the skill name or file path is wrong, list skills or retry with a bundle-relative path.",
|
|
1805
|
+
input_preview_formatter=_preview_skill_read,
|
|
1806
|
+
output_summary_formatter=_summary_skill_read,
|
|
1807
|
+
),
|
|
1808
|
+
built_in_subagent_exposure="readonly",
|
|
1809
|
+
),
|
|
1810
|
+
BuiltinToolMetadata(
|
|
1811
|
+
name="image_generate",
|
|
1812
|
+
description=(
|
|
1813
|
+
"Generate one to four raster image assets through the configured "
|
|
1814
|
+
"OpenAI-compatible image provider and write them under the workspace root. "
|
|
1815
|
+
"This is an external billable operation and is exposed only when "
|
|
1816
|
+
"image_generation.enabled=true. Output paths must be new .png, .jpg, .jpeg, "
|
|
1817
|
+
"or .webp files; existing files are never overwritten. The result includes "
|
|
1818
|
+
"dimensions, format, alpha-channel presence, byte count, SHA-256, provider "
|
|
1819
|
+
"request metadata, and technical validation status."
|
|
1820
|
+
),
|
|
1821
|
+
parameters={
|
|
1822
|
+
"type": "object",
|
|
1823
|
+
"properties": {
|
|
1824
|
+
"prompt": {
|
|
1825
|
+
"type": "string",
|
|
1826
|
+
"minLength": 1,
|
|
1827
|
+
"maxLength": 32000,
|
|
1828
|
+
"description": (
|
|
1829
|
+
"Concrete production brief covering subject, composition, style, "
|
|
1830
|
+
"palette, lighting, negative constraints, and intended use."
|
|
1831
|
+
),
|
|
1832
|
+
},
|
|
1833
|
+
"output_path": {
|
|
1834
|
+
"type": "string",
|
|
1835
|
+
"description": (
|
|
1836
|
+
"New workspace-root-relative .png, .jpg, .jpeg, or .webp path. "
|
|
1837
|
+
"For count > 1, -1, -2, etc. are appended before the extension."
|
|
1838
|
+
),
|
|
1839
|
+
},
|
|
1840
|
+
"count": {
|
|
1841
|
+
"type": "integer",
|
|
1842
|
+
"minimum": 1,
|
|
1843
|
+
"maximum": 4,
|
|
1844
|
+
"default": 1,
|
|
1845
|
+
},
|
|
1846
|
+
"size": {
|
|
1847
|
+
"type": "string",
|
|
1848
|
+
"enum": ["auto", "1024x1024", "1536x1024", "1024x1536"],
|
|
1849
|
+
"default": "auto",
|
|
1850
|
+
},
|
|
1851
|
+
"quality": {
|
|
1852
|
+
"type": "string",
|
|
1853
|
+
"enum": ["auto", "low", "medium", "high"],
|
|
1854
|
+
"default": "auto",
|
|
1855
|
+
},
|
|
1856
|
+
"background": {
|
|
1857
|
+
"type": "string",
|
|
1858
|
+
"enum": ["auto", "opaque", "transparent"],
|
|
1859
|
+
"default": "auto",
|
|
1860
|
+
},
|
|
1861
|
+
},
|
|
1862
|
+
"required": ["prompt", "output_path"],
|
|
1863
|
+
},
|
|
1864
|
+
categories=("write", "image", "generation", "network"),
|
|
1865
|
+
rich=RichToolMetadata(
|
|
1866
|
+
display_name="Generate Image",
|
|
1867
|
+
reasoning_hint="Create a production raster asset from a precise visual brief.",
|
|
1868
|
+
action_hint="Generate, validate, and atomically write a new image asset.",
|
|
1869
|
+
fallback_hint=(
|
|
1870
|
+
"If unavailable, report the missing image-generation configuration; do not "
|
|
1871
|
+
"substitute placeholder bytes or claim an asset was generated."
|
|
1872
|
+
),
|
|
1873
|
+
input_preview_formatter=_preview_image_generate,
|
|
1874
|
+
output_summary_formatter=_summary_image_generate,
|
|
1875
|
+
),
|
|
1876
|
+
optional=True,
|
|
1877
|
+
optional_unavailable_reason="image_generation.enabled is false",
|
|
1878
|
+
),
|
|
1879
|
+
BuiltinToolMetadata(
|
|
1880
|
+
name="verify_run",
|
|
1881
|
+
description=(
|
|
1882
|
+
"Run configured verification commands. Prefer for tests/lint/build. "
|
|
1883
|
+
"If overriding commands, pass one verifier per array item; do not join with "
|
|
1884
|
+
"&&, ;, pipes, filters, list/build-only checks, or swapped build systems."
|
|
1885
|
+
),
|
|
1886
|
+
parameters={
|
|
1887
|
+
"type": "object",
|
|
1888
|
+
"properties": {
|
|
1889
|
+
"commands": {"type": "array", "items": {"type": "string"}},
|
|
1890
|
+
},
|
|
1891
|
+
"required": [],
|
|
1892
|
+
},
|
|
1893
|
+
categories=("verify",),
|
|
1894
|
+
rich=RichToolMetadata(
|
|
1895
|
+
display_name="Run Verification",
|
|
1896
|
+
reasoning_hint="Run structured verification before relying on raw shell commands.",
|
|
1897
|
+
action_hint="Execute configured or targeted verification commands and inspect pass/fail results.",
|
|
1898
|
+
fallback_hint="If using shell_run, run the same unfiltered verifier.",
|
|
1899
|
+
input_preview_formatter=_preview_verify_run,
|
|
1900
|
+
output_summary_formatter=_summary_verify_run,
|
|
1901
|
+
),
|
|
1902
|
+
),
|
|
1903
|
+
BuiltinToolMetadata(
|
|
1904
|
+
name="shell_run",
|
|
1905
|
+
description="Run a shell command under the working root (policy-checked).",
|
|
1906
|
+
parameters={
|
|
1907
|
+
"type": "object",
|
|
1908
|
+
"properties": {
|
|
1909
|
+
"cmd": {"type": "string"},
|
|
1910
|
+
"cwd": {"type": "string"},
|
|
1911
|
+
"cwd_base": _cwd_base_property(),
|
|
1912
|
+
},
|
|
1913
|
+
"required": ["cmd"],
|
|
1914
|
+
},
|
|
1915
|
+
categories=("shell",),
|
|
1916
|
+
rich=RichToolMetadata(
|
|
1917
|
+
display_name="Run Command",
|
|
1918
|
+
reasoning_hint="Validate assumptions with project commands.",
|
|
1919
|
+
action_hint="Run command and inspect exit code/stdout/stderr.",
|
|
1920
|
+
fallback_hint="If denied/failing, use safer command or ask approval.",
|
|
1921
|
+
input_preview_formatter=_preview_shell_run,
|
|
1922
|
+
output_summary_formatter=_summary_shell_run,
|
|
1923
|
+
),
|
|
1924
|
+
),
|
|
1925
|
+
BuiltinToolMetadata(
|
|
1926
|
+
name="shell_background",
|
|
1927
|
+
description=(
|
|
1928
|
+
"Start a long-running shell command in the background under the working root "
|
|
1929
|
+
"(policy-checked). Returns a process_id you can use with shell_output, shell_kill, "
|
|
1930
|
+
"and shell_list. It is terminated when the session closes. Use this for dev servers "
|
|
1931
|
+
"you only need while this session is running, file watchers, log tailers, or any "
|
|
1932
|
+
"command that should not block the agent loop."
|
|
1933
|
+
),
|
|
1934
|
+
parameters={
|
|
1935
|
+
"type": "object",
|
|
1936
|
+
"properties": {
|
|
1937
|
+
"cmd": {"type": "string"},
|
|
1938
|
+
"cwd": {"type": "string"},
|
|
1939
|
+
"cwd_base": _cwd_base_property(),
|
|
1940
|
+
"persist": {
|
|
1941
|
+
"type": "boolean",
|
|
1942
|
+
"default": False,
|
|
1943
|
+
"description": "Keep the process running after the session ends, as a durable service.",
|
|
1944
|
+
},
|
|
1945
|
+
"probe_port": {
|
|
1946
|
+
"type": "integer",
|
|
1947
|
+
"minimum": 1,
|
|
1948
|
+
"maximum": 65535,
|
|
1949
|
+
"description": "TCP port to probe for liveness; inferred from cmd when omitted.",
|
|
1950
|
+
},
|
|
1951
|
+
},
|
|
1952
|
+
"required": ["cmd"],
|
|
1953
|
+
},
|
|
1954
|
+
categories=("shell", "background"),
|
|
1955
|
+
rich=RichToolMetadata(
|
|
1956
|
+
display_name="Run Background Command",
|
|
1957
|
+
reasoning_hint=(
|
|
1958
|
+
"Spawn a non-blocking process for long-running work without holding the agent loop."
|
|
1959
|
+
),
|
|
1960
|
+
action_hint="Start command and capture process_id; later read incrementally with shell_output.",
|
|
1961
|
+
fallback_hint="If the command can complete fast, prefer shell_run for direct stdout.",
|
|
1962
|
+
input_preview_formatter=_preview_shell_run,
|
|
1963
|
+
output_summary_formatter=_summary_shell_background,
|
|
1964
|
+
),
|
|
1965
|
+
),
|
|
1966
|
+
BuiltinToolMetadata(
|
|
1967
|
+
name="shell_output",
|
|
1968
|
+
description=(
|
|
1969
|
+
"Read accumulated stdout/stderr from a background process started with shell_background. "
|
|
1970
|
+
"Pass since=<next_seq> from the previous read to get only new lines. For a quiet "
|
|
1971
|
+
"long-running process, prefer shell_wait instead of repeatedly polling shell_output. "
|
|
1972
|
+
"Output is ring-buffered; very chatty processes may report dropped_lines > 0. "
|
|
1973
|
+
"Use the process_id returned by shell_background or shell_list, not a tool_call_id; "
|
|
1974
|
+
"unknown ids return structured recovery guidance."
|
|
1975
|
+
),
|
|
1976
|
+
parameters={
|
|
1977
|
+
"type": "object",
|
|
1978
|
+
"properties": {
|
|
1979
|
+
"process_id": {"type": "string"},
|
|
1980
|
+
"since": {"type": "integer", "default": 0},
|
|
1981
|
+
},
|
|
1982
|
+
"required": ["process_id"],
|
|
1983
|
+
},
|
|
1984
|
+
categories=("shell", "background", "read"),
|
|
1985
|
+
rich=RichToolMetadata(
|
|
1986
|
+
display_name="Read Background Output",
|
|
1987
|
+
reasoning_hint="Inspect what a background process has emitted since the last read.",
|
|
1988
|
+
action_hint="Fetch new output lines plus current status, exit_code, and runtime.",
|
|
1989
|
+
fallback_hint="If process_id is unknown, list active processes with shell_list first.",
|
|
1990
|
+
input_preview_formatter=_preview_shell_output,
|
|
1991
|
+
output_summary_formatter=_summary_shell_output,
|
|
1992
|
+
),
|
|
1993
|
+
),
|
|
1994
|
+
BuiltinToolMetadata(
|
|
1995
|
+
name="shell_wait",
|
|
1996
|
+
description=(
|
|
1997
|
+
"Wait inside one bounded tool call for a background process to emit new output, exit, "
|
|
1998
|
+
"or either condition. Use this instead of repeatedly polling shell_output when no new "
|
|
1999
|
+
"output is available. Use the process_id returned by shell_background or shell_list, "
|
|
2000
|
+
"not a tool_call_id; unknown ids return structured recovery guidance."
|
|
2001
|
+
),
|
|
2002
|
+
parameters={
|
|
2003
|
+
"type": "object",
|
|
2004
|
+
"properties": {
|
|
2005
|
+
"process_id": {"type": "string"},
|
|
2006
|
+
"since": {"type": "integer", "default": 0},
|
|
2007
|
+
"wait_seconds": {"type": "number", "default": 5.0, "minimum": 0, "maximum": 60},
|
|
2008
|
+
"until": {
|
|
2009
|
+
"type": "string",
|
|
2010
|
+
"enum": ["output_available", "process_exited", "either"],
|
|
2011
|
+
"default": "either",
|
|
2012
|
+
},
|
|
2013
|
+
"max_bytes": {"type": "integer", "default": 12000, "minimum": 1},
|
|
2014
|
+
},
|
|
2015
|
+
"required": ["process_id"],
|
|
2016
|
+
},
|
|
2017
|
+
categories=("shell", "background", "read"),
|
|
2018
|
+
rich=RichToolMetadata(
|
|
2019
|
+
display_name="Wait For Background Output",
|
|
2020
|
+
reasoning_hint="Block briefly for useful output or process completion without busy polling.",
|
|
2021
|
+
action_hint="Wait for new output, exit, or either; returns the same cursor/status fields as shell_output.",
|
|
2022
|
+
fallback_hint="If the process_id is unknown, list active processes with shell_list first.",
|
|
2023
|
+
input_preview_formatter=_preview_shell_output,
|
|
2024
|
+
output_summary_formatter=_summary_shell_wait,
|
|
2025
|
+
),
|
|
2026
|
+
),
|
|
2027
|
+
BuiltinToolMetadata(
|
|
2028
|
+
name="shell_kill",
|
|
2029
|
+
description=(
|
|
2030
|
+
"Terminate a background process by process_id. Sends SIGTERM (or platform equivalent), "
|
|
2031
|
+
"escalates to SIGKILL after the configured grace period. Idempotent - calling on an "
|
|
2032
|
+
"already-terminated process returns the existing status."
|
|
2033
|
+
),
|
|
2034
|
+
parameters={
|
|
2035
|
+
"type": "object",
|
|
2036
|
+
"properties": {
|
|
2037
|
+
"process_id": {"type": "string"},
|
|
2038
|
+
},
|
|
2039
|
+
"required": ["process_id"],
|
|
2040
|
+
},
|
|
2041
|
+
categories=("shell", "background"),
|
|
2042
|
+
rich=RichToolMetadata(
|
|
2043
|
+
display_name="Kill Background Process",
|
|
2044
|
+
reasoning_hint="Stop a background process when no longer needed or to free a slot.",
|
|
2045
|
+
action_hint="Signal the process; output remains readable after termination.",
|
|
2046
|
+
fallback_hint="If kill fails, the session lifecycle reaps remaining processes on close.",
|
|
2047
|
+
input_preview_formatter=_preview_shell_kill,
|
|
2048
|
+
output_summary_formatter=_summary_shell_kill,
|
|
2049
|
+
),
|
|
2050
|
+
),
|
|
2051
|
+
BuiltinToolMetadata(
|
|
2052
|
+
name="shell_list",
|
|
2053
|
+
description=(
|
|
2054
|
+
"List all background processes from this session with their status, exit code, "
|
|
2055
|
+
"command preview (truncated), cwd, and runtime. Includes both running and recently-"
|
|
2056
|
+
"terminated processes that have not yet been pruned."
|
|
2057
|
+
),
|
|
2058
|
+
parameters={"type": "object", "properties": {}, "required": []},
|
|
2059
|
+
categories=("shell", "background", "read"),
|
|
2060
|
+
rich=RichToolMetadata(
|
|
2061
|
+
display_name="List Background Processes",
|
|
2062
|
+
reasoning_hint="Audit current background activity before starting more work.",
|
|
2063
|
+
action_hint="Enumerate active and recently-terminated bg processes.",
|
|
2064
|
+
fallback_hint="If empty, no bg processes are tracked in this session.",
|
|
2065
|
+
output_summary_formatter=_summary_shell_list,
|
|
2066
|
+
),
|
|
2067
|
+
),
|
|
2068
|
+
BuiltinToolMetadata(
|
|
2069
|
+
name="workspace_preview_start",
|
|
2070
|
+
description=(
|
|
2071
|
+
"Start Alysis Code's constrained static-file preview for a workspace directory. "
|
|
2072
|
+
"The model chooses semantic access (auto, local, or lan); the runtime discovers a "
|
|
2073
|
+
"suitable interface and allocates a free port when none is requested. LAN previews "
|
|
2074
|
+
"require approval and temporary authentication. The server does not require Docker, "
|
|
2075
|
+
"disables directory listing, blocks hidden files, and rejects symlink escapes."
|
|
2076
|
+
),
|
|
2077
|
+
parameters={
|
|
2078
|
+
"type": "object",
|
|
2079
|
+
"properties": {
|
|
2080
|
+
"cwd": {"type": "string"},
|
|
2081
|
+
"cwd_base": _cwd_base_property(),
|
|
2082
|
+
"access": {
|
|
2083
|
+
"type": "string",
|
|
2084
|
+
"enum": ["auto", "local", "lan"],
|
|
2085
|
+
"default": "auto",
|
|
2086
|
+
},
|
|
2087
|
+
"port": {"type": "integer", "minimum": 1, "maximum": 65535},
|
|
2088
|
+
},
|
|
2089
|
+
"required": [],
|
|
2090
|
+
},
|
|
2091
|
+
categories=("shell", "background", "service"),
|
|
2092
|
+
rich=RichToolMetadata(
|
|
2093
|
+
display_name="Start Workspace Preview",
|
|
2094
|
+
reasoning_hint=(
|
|
2095
|
+
"Use auto unless the user explicitly asks for local-only or LAN access."
|
|
2096
|
+
),
|
|
2097
|
+
action_hint=(
|
|
2098
|
+
"Let the runtime allocate the endpoint, then use the returned access_url."
|
|
2099
|
+
),
|
|
2100
|
+
fallback_hint=(
|
|
2101
|
+
"If a requested port is occupied, omit it so the operating system chooses one."
|
|
2102
|
+
),
|
|
2103
|
+
output_summary_formatter=_summary_shell_service,
|
|
2104
|
+
),
|
|
2105
|
+
),
|
|
2106
|
+
BuiltinToolMetadata(
|
|
2107
|
+
name="shell_service_start",
|
|
2108
|
+
description=(
|
|
2109
|
+
"Start an explicit durable service under the working root. Unlike shell_background, "
|
|
2110
|
+
"this service keeps running after the session ends and must be stopped with "
|
|
2111
|
+
"shell_service_stop when no longer needed. For static HTML/CSS/JS previews, use "
|
|
2112
|
+
"workspace_preview_start instead. Provide readiness when another server or daemon "
|
|
2113
|
+
"must remain available after finalization."
|
|
2114
|
+
),
|
|
2115
|
+
parameters={
|
|
2116
|
+
"type": "object",
|
|
2117
|
+
"properties": {
|
|
2118
|
+
"cmd": {"type": "string"},
|
|
2119
|
+
"cwd": {"type": "string"},
|
|
2120
|
+
"cwd_base": _cwd_base_property(),
|
|
2121
|
+
"readiness": _service_readiness_property(),
|
|
2122
|
+
},
|
|
2123
|
+
"required": ["cmd"],
|
|
2124
|
+
},
|
|
2125
|
+
categories=("shell", "background", "service"),
|
|
2126
|
+
rich=RichToolMetadata(
|
|
2127
|
+
display_name="Start Durable Service",
|
|
2128
|
+
reasoning_hint="Use only when the task explicitly requires service persistence.",
|
|
2129
|
+
action_hint="Start the service and check the returned readiness/status fields.",
|
|
2130
|
+
fallback_hint=(
|
|
2131
|
+
"Use workspace_preview_start for static sites, or shell_background for temporary "
|
|
2132
|
+
"non-preview processes that should be reaped when the session closes."
|
|
2133
|
+
),
|
|
2134
|
+
input_preview_formatter=_preview_shell_run,
|
|
2135
|
+
output_summary_formatter=_summary_shell_service,
|
|
2136
|
+
),
|
|
2137
|
+
),
|
|
2138
|
+
BuiltinToolMetadata(
|
|
2139
|
+
name="shell_service_status",
|
|
2140
|
+
description=(
|
|
2141
|
+
"Check a durable service by service_id and re-run its readiness probe. Use before "
|
|
2142
|
+
"finalization for tasks that require a persistent server or daemon."
|
|
2143
|
+
),
|
|
2144
|
+
parameters={
|
|
2145
|
+
"type": "object",
|
|
2146
|
+
"properties": {
|
|
2147
|
+
"service_id": {"type": "string"},
|
|
2148
|
+
},
|
|
2149
|
+
"required": ["service_id"],
|
|
2150
|
+
},
|
|
2151
|
+
categories=("shell", "background", "service", "read"),
|
|
2152
|
+
rich=RichToolMetadata(
|
|
2153
|
+
display_name="Check Durable Service",
|
|
2154
|
+
reasoning_hint="Verify a durable service is still alive and ready.",
|
|
2155
|
+
action_hint="Re-check status/readiness for a previously started durable service.",
|
|
2156
|
+
fallback_hint="If the service is stale or unknown, start it explicitly with readiness.",
|
|
2157
|
+
input_preview_formatter=_preview_shell_service_id,
|
|
2158
|
+
output_summary_formatter=_summary_shell_service,
|
|
2159
|
+
),
|
|
2160
|
+
),
|
|
2161
|
+
BuiltinToolMetadata(
|
|
2162
|
+
name="shell_service_stop",
|
|
2163
|
+
description=(
|
|
2164
|
+
"Stop a durable service by service_id. Uses stored PID/PGID identity metadata to avoid "
|
|
2165
|
+
"killing unrelated processes if the PID has been reused."
|
|
2166
|
+
),
|
|
2167
|
+
parameters={
|
|
2168
|
+
"type": "object",
|
|
2169
|
+
"properties": {
|
|
2170
|
+
"service_id": {"type": "string"},
|
|
2171
|
+
},
|
|
2172
|
+
"required": ["service_id"],
|
|
2173
|
+
},
|
|
2174
|
+
categories=("shell", "background", "service"),
|
|
2175
|
+
rich=RichToolMetadata(
|
|
2176
|
+
display_name="Stop Durable Service",
|
|
2177
|
+
reasoning_hint="Clean up a durable service once persistence is no longer required.",
|
|
2178
|
+
action_hint="Terminate the durable service and remove its runtime metadata.",
|
|
2179
|
+
fallback_hint="If the service is unknown, inspect status or runtime logs first.",
|
|
2180
|
+
input_preview_formatter=_preview_shell_service_id,
|
|
2181
|
+
output_summary_formatter=_summary_shell_service,
|
|
2182
|
+
),
|
|
2183
|
+
),
|
|
2184
|
+
BuiltinToolMetadata(
|
|
2185
|
+
name="session_set_workdir",
|
|
2186
|
+
description=(
|
|
2187
|
+
"Change the live session active_workdir inside the bound workspace_root so later relative "
|
|
2188
|
+
"file, search, and shell calls default there. Use this when the user says things like "
|
|
2189
|
+
"'go to packages/app', 'work in apps/web', or 'switch to server/api and inspect package.json'."
|
|
2190
|
+
),
|
|
2191
|
+
parameters={
|
|
2192
|
+
"type": "object",
|
|
2193
|
+
"properties": {
|
|
2194
|
+
"path": {
|
|
2195
|
+
"type": "string",
|
|
2196
|
+
"description": (
|
|
2197
|
+
"Target directory inside the current workspace_root. Relative values resolve "
|
|
2198
|
+
"from the current active_workdir."
|
|
2199
|
+
),
|
|
2200
|
+
},
|
|
2201
|
+
},
|
|
2202
|
+
"required": ["path"],
|
|
2203
|
+
},
|
|
2204
|
+
categories=("session", "navigation"),
|
|
2205
|
+
rich=RichToolMetadata(
|
|
2206
|
+
display_name="Set Session Workdir",
|
|
2207
|
+
reasoning_hint=(
|
|
2208
|
+
"Move the session's live default workdir before more file, search, or shell calls."
|
|
2209
|
+
),
|
|
2210
|
+
action_hint=(
|
|
2211
|
+
"Change the active workdir inside the current workspace_root when the user asks to "
|
|
2212
|
+
"go to packages/app, work in apps/web, or switch to another directory."
|
|
2213
|
+
),
|
|
2214
|
+
fallback_hint="If the path escapes the workspace or does not exist, report that blocker clearly.",
|
|
2215
|
+
input_preview_formatter=_preview_single_path,
|
|
2216
|
+
output_summary_formatter=_summary_session_set_workdir,
|
|
2217
|
+
),
|
|
2218
|
+
),
|
|
2219
|
+
BuiltinToolMetadata(
|
|
2220
|
+
name="git_status",
|
|
2221
|
+
description="Run git status (porcelain) in the working root. Prefer before/after edits to inspect repo state.",
|
|
2222
|
+
parameters={"type": "object", "properties": {}, "required": []},
|
|
2223
|
+
categories=("read", "git"),
|
|
2224
|
+
rich=RichToolMetadata(
|
|
2225
|
+
display_name="Git Status",
|
|
2226
|
+
reasoning_hint="Check repository state before/after edits.",
|
|
2227
|
+
action_hint="Inspect tracked/untracked and dirty changes.",
|
|
2228
|
+
fallback_hint="If unavailable, continue with file-based checks.",
|
|
2229
|
+
output_summary_formatter=_summary_git_status,
|
|
2230
|
+
),
|
|
2231
|
+
built_in_subagent_exposure="readonly",
|
|
2232
|
+
),
|
|
2233
|
+
BuiltinToolMetadata(
|
|
2234
|
+
name="git_diff",
|
|
2235
|
+
description="Run git diff in the working root. Prefer to review current repo changes before the final response.",
|
|
2236
|
+
parameters={"type": "object", "properties": {}, "required": []},
|
|
2237
|
+
categories=("read", "git"),
|
|
2238
|
+
rich=RichToolMetadata(
|
|
2239
|
+
display_name="Git Diff",
|
|
2240
|
+
reasoning_hint="Review change impact before final response.",
|
|
2241
|
+
action_hint="Collect current diff for inspection.",
|
|
2242
|
+
fallback_hint="If unavailable, rely on patch/tool summaries.",
|
|
2243
|
+
output_summary_formatter=_summary_git_diff,
|
|
2244
|
+
),
|
|
2245
|
+
built_in_subagent_exposure="readonly",
|
|
2246
|
+
),
|
|
2247
|
+
BuiltinToolMetadata(
|
|
2248
|
+
name="git_history",
|
|
2249
|
+
description=(
|
|
2250
|
+
"Inspect Git history: log lists commits, show reads one revision, and blame "
|
|
2251
|
+
"maps lines. Use git_diff for uncommitted changes."
|
|
2252
|
+
),
|
|
2253
|
+
parameters={
|
|
2254
|
+
"type": "object",
|
|
2255
|
+
"properties": {
|
|
2256
|
+
"mode": {"type": "string", "enum": ["log", "show", "blame"]},
|
|
2257
|
+
"path": {"type": "string"},
|
|
2258
|
+
"limit": {"type": "integer", "default": 10},
|
|
2259
|
+
"ref": {
|
|
2260
|
+
"type": "string",
|
|
2261
|
+
"description": "Log revision; show alias for commit.",
|
|
2262
|
+
},
|
|
2263
|
+
"grep": {"type": "string"},
|
|
2264
|
+
"author": {"type": "string"},
|
|
2265
|
+
"commit": {
|
|
2266
|
+
"type": "string",
|
|
2267
|
+
"description": "Show revision; ref is an alias.",
|
|
2268
|
+
},
|
|
2269
|
+
"start_line": {"type": "integer"},
|
|
2270
|
+
"end_line": {"type": "integer"},
|
|
2271
|
+
},
|
|
2272
|
+
"required": ["mode"],
|
|
2273
|
+
},
|
|
2274
|
+
categories=("read", "git", "history"),
|
|
2275
|
+
rich=RichToolMetadata(
|
|
2276
|
+
display_name="Git History",
|
|
2277
|
+
reasoning_hint="Inspect repository history without dropping to shell commands.",
|
|
2278
|
+
action_hint="Use one tool for commit logs, commit excerpts, or blame ranges.",
|
|
2279
|
+
fallback_hint="If the commit/path/range is wrong, narrow the request and retry.",
|
|
2280
|
+
input_preview_formatter=_preview_git_history,
|
|
2281
|
+
output_summary_formatter=_summary_git_history,
|
|
2282
|
+
),
|
|
2283
|
+
built_in_subagent_exposure="readonly",
|
|
2284
|
+
),
|
|
2285
|
+
BuiltinToolMetadata(
|
|
2286
|
+
name="git_apply_patch",
|
|
2287
|
+
description="Apply a unified diff patch using git apply. Prefer for broader, multi-file, or context-heavy edits where unified diff context matters.",
|
|
2288
|
+
parameters={
|
|
2289
|
+
"type": "object",
|
|
2290
|
+
"properties": {"patch": {"type": "string"}},
|
|
2291
|
+
"required": ["patch"],
|
|
2292
|
+
},
|
|
2293
|
+
categories=("write", "git"),
|
|
2294
|
+
rich=RichToolMetadata(
|
|
2295
|
+
display_name="Apply Patch",
|
|
2296
|
+
reasoning_hint="Apply multi-file edits atomically.",
|
|
2297
|
+
action_hint="Run patch application and validate outcome.",
|
|
2298
|
+
fallback_hint="If patch fails, retry with smaller focused patch.",
|
|
2299
|
+
output_summary_formatter=_summary_git_apply_patch,
|
|
2300
|
+
),
|
|
2301
|
+
),
|
|
2302
|
+
BuiltinToolMetadata(
|
|
2303
|
+
name="subagent_run",
|
|
2304
|
+
description=(
|
|
2305
|
+
"Run a registered subagent in an isolated nested session and return its single "
|
|
2306
|
+
"final report. Each call spawns a fresh subagent with its own system prompt, "
|
|
2307
|
+
"tool sandbox, execution policy, and message history; the subagent does not see this "
|
|
2308
|
+
"conversation's transcript and you do not see its intermediate steps -- only the "
|
|
2309
|
+
"final text it produced plus structured metadata.\n"
|
|
2310
|
+
"\n"
|
|
2311
|
+
"Strategic guidance for when to delegate, parallelism, and trust-but-verify is "
|
|
2312
|
+
"covered in the `Subagent delegation` section of the system prompt; this "
|
|
2313
|
+
"description focuses on the tool contract.\n"
|
|
2314
|
+
"\n"
|
|
2315
|
+
"Parallel batch contract\n"
|
|
2316
|
+
"- To run independent investigations concurrently, emit two or more "
|
|
2317
|
+
"`subagent_run` calls in the same assistant response and emit no other tool call "
|
|
2318
|
+
"in that response. Each call must be shared+readonly or isolated. The host "
|
|
2319
|
+
"runs at most four children concurrently, queues any excess, and returns results "
|
|
2320
|
+
"in original order; shared writes or mixed non-subagent calls run sequentially.\n"
|
|
2321
|
+
"\n"
|
|
2322
|
+
"Parameters\n"
|
|
2323
|
+
"- name (required, string): registered subagent name. Built-in names include "
|
|
2324
|
+
"`explorer`, `implementer`, `frontend-engineer`, `debugger`, "
|
|
2325
|
+
"`code-reviewer`, `verifier`, and, when image generation is enabled, "
|
|
2326
|
+
"`visual-designer`. Project-level custom subagents "
|
|
2327
|
+
"from `.alysis_agents/*.md` and user-level ones from the user config dir are "
|
|
2328
|
+
"also resolvable; manual-only roles remain host-resolvable but are omitted from "
|
|
2329
|
+
"autonomous discovery surfaces. Names are case-insensitive "
|
|
2330
|
+
"and a small alias table is applied "
|
|
2331
|
+
"(e.g. `explore` -> `explorer`). Unknown names return an `error` field plus the "
|
|
2332
|
+
"list of available names.\n"
|
|
2333
|
+
"- task (required, string): the self-contained brief for the subagent. Treat the "
|
|
2334
|
+
"subagent like a smart colleague who just walked into the room: it has no memory "
|
|
2335
|
+
"of this conversation and has read no files yet. A good `task` includes (1) the "
|
|
2336
|
+
"goal in one sentence, (2) exact repo-root-relative paths or symbols to start "
|
|
2337
|
+
"from when known, (3) what you already learned or ruled out, and (4) the shape "
|
|
2338
|
+
"of answer you want (e.g. `list 3-5 candidate files`, `verdict + blocking "
|
|
2339
|
+
"issues`, `under 250 words`). Terse command-style prompts produce shallow output.\n"
|
|
2340
|
+
"- mode (optional, string): one of `readonly`, `review`, `auto`, `fullaccess`. "
|
|
2341
|
+
"Defaults to the subagent's declared mode. The effective mode is clamped to be "
|
|
2342
|
+
"no more permissive than the parent session's mode, and is never raised above "
|
|
2343
|
+
"`auto` unless the parent session itself is `fullaccess`. You cannot use this to "
|
|
2344
|
+
"escalate privileges.\n"
|
|
2345
|
+
"- max_steps (optional, integer): explicit safety limit on the subagent's "
|
|
2346
|
+
"agentic iterations. If omitted, autonomous subagents continue until they finish, "
|
|
2347
|
+
"are cancelled, become blocked, or encounter a fatal error.\n"
|
|
2348
|
+
"\n"
|
|
2349
|
+
"Output shape on success\n"
|
|
2350
|
+
"Returns an object with: `subagent` (resolved name), `subagent_session_id`, "
|
|
2351
|
+
"`result` (the subagent's final assistant text -- this is your primary signal), "
|
|
2352
|
+
"`usage` (token/cost totals already merged into the parent session's usage), "
|
|
2353
|
+
"and `sandbox` (the effective mode and the list of tools the "
|
|
2354
|
+
"subagent had after allow/deny filtering).\n"
|
|
2355
|
+
"\n"
|
|
2356
|
+
"Output shape on failure\n"
|
|
2357
|
+
"Returns an object with `error` (string) and, when applicable, `available_subagents`, "
|
|
2358
|
+
"`subagent_session_id`, `exit_code`, `usage`, and `final_text` (best-effort partial "
|
|
2359
|
+
"output). Common causes: unknown name, subagents disabled for the session, nested "
|
|
2360
|
+
"delegation attempted outside the bounded helper contract, no tools "
|
|
2361
|
+
"remained after sandbox filtering, the nested session raised, or the subagent ended "
|
|
2362
|
+
"without an authoritative final-report signal. Plain assistant transcript text "
|
|
2363
|
+
"without that final signal is treated as degraded rather than success.\n"
|
|
2364
|
+
"Set `workspace_view=isolated` for a HEAD-based worktree retained for apply/discard.\n"
|
|
2365
|
+
"\n"
|
|
2366
|
+
"Sandboxing facts\n"
|
|
2367
|
+
"- Each subagent definition declares an allow-list and/or deny-list of tools; "
|
|
2368
|
+
"the resulting toolset may be smaller than your own. Do not assume the subagent "
|
|
2369
|
+
"has every tool you have.\n"
|
|
2370
|
+
"- Eligible depth-1 write-capable children may consult bounded read-only helpers; "
|
|
2371
|
+
"helpers and all other recursive delegation are blocked.\n"
|
|
2372
|
+
"- A subagent may run on a different model and temperature than this session, "
|
|
2373
|
+
"controlled by its `model` / `model_role` definition fields.\n"
|
|
2374
|
+
"\n"
|
|
2375
|
+
"Examples of `task`\n"
|
|
2376
|
+
'Good: "Map how API authentication flows from request ingress to user '
|
|
2377
|
+
"resolution. Start from src/api/server.py and src/auth/. I have already "
|
|
2378
|
+
"confirmed the JWT lib is PyJWT. Report: the call chain (file:line for each "
|
|
2379
|
+
"step), where session state is stored, and any auth checks that look "
|
|
2380
|
+
'inconsistent. Under 250 words."\n'
|
|
2381
|
+
'Bad: "look at the auth code"'
|
|
2382
|
+
),
|
|
2383
|
+
parameters={
|
|
2384
|
+
"type": "object",
|
|
2385
|
+
"properties": {
|
|
2386
|
+
"name": {
|
|
2387
|
+
"type": "string",
|
|
2388
|
+
"description": (
|
|
2389
|
+
"Registered subagent name. Built-in: explorer, implementer, "
|
|
2390
|
+
"frontend-engineer, debugger, verifier, code-reviewer, "
|
|
2391
|
+
"dependency-scout; "
|
|
2392
|
+
"visual-designer is available when image generation is enabled. "
|
|
2393
|
+
"Project-defined custom names from "
|
|
2394
|
+
".alysis_agents/ are also valid. "
|
|
2395
|
+
"Case-insensitive."
|
|
2396
|
+
),
|
|
2397
|
+
},
|
|
2398
|
+
"task": {
|
|
2399
|
+
"type": "string",
|
|
2400
|
+
"description": (
|
|
2401
|
+
"Self-contained brief for the subagent. The "
|
|
2402
|
+
"subagent has no memory of this conversation "
|
|
2403
|
+
"and has read no files yet -- include the "
|
|
2404
|
+
"goal, exact paths or symbols to start from, "
|
|
2405
|
+
"what you have ruled out, and the form of "
|
|
2406
|
+
"answer you want. See tool description for "
|
|
2407
|
+
"full guidance."
|
|
2408
|
+
),
|
|
2409
|
+
},
|
|
2410
|
+
"mode": {
|
|
2411
|
+
"type": "string",
|
|
2412
|
+
"enum": ["readonly", "review", "auto", "fullaccess"],
|
|
2413
|
+
"description": (
|
|
2414
|
+
"Optional mode override "
|
|
2415
|
+
"(readonly|review|auto|fullaccess). Clamped "
|
|
2416
|
+
"to the parent session's mode and never "
|
|
2417
|
+
"escalated above auto unless the parent is "
|
|
2418
|
+
"fullaccess."
|
|
2419
|
+
),
|
|
2420
|
+
},
|
|
2421
|
+
"max_steps": {
|
|
2422
|
+
"type": "integer",
|
|
2423
|
+
"description": (
|
|
2424
|
+
"Optional safety limit on the subagent's agentic iterations. "
|
|
2425
|
+
"If omitted, autonomous execution has no step ceiling."
|
|
2426
|
+
),
|
|
2427
|
+
},
|
|
2428
|
+
"workspace_view": {
|
|
2429
|
+
"type": "string",
|
|
2430
|
+
"enum": ["shared", "isolated"],
|
|
2431
|
+
"description": "Workspace view; isolated starts from parent HEAD.",
|
|
2432
|
+
},
|
|
2433
|
+
"workspace_from_run": {
|
|
2434
|
+
"type": "string",
|
|
2435
|
+
"description": "Completed isolated run id for a non-writing child.",
|
|
2436
|
+
},
|
|
2437
|
+
},
|
|
2438
|
+
"required": ["name", "task"],
|
|
2439
|
+
},
|
|
2440
|
+
categories=("subagent",),
|
|
2441
|
+
rich=RichToolMetadata(
|
|
2442
|
+
display_name="Run Subagent",
|
|
2443
|
+
reasoning_hint="Delegate focused repository analysis to a specialized subagent.",
|
|
2444
|
+
action_hint="Run nested subagent session and consume the final summarized result.",
|
|
2445
|
+
fallback_hint="If unclear or low confidence, verify claims with direct tools before continuing.",
|
|
2446
|
+
output_summary_formatter=_summary_subagent_run,
|
|
2447
|
+
),
|
|
2448
|
+
),
|
|
2449
|
+
)
|
|
2450
|
+
|
|
2451
|
+
_SUBAGENT_RUN_PARAMETERS = next(
|
|
2452
|
+
spec.parameters for spec in _BUILTIN_TOOL_METADATA if spec.name == "subagent_run"
|
|
2453
|
+
)
|
|
2454
|
+
_SUBAGENT_SPAWN_PARAMETERS = copy.deepcopy(_SUBAGENT_RUN_PARAMETERS)
|
|
2455
|
+
_SUBAGENT_SPAWN_PARAMETERS["properties"]["run_id"] = {
|
|
2456
|
+
"type": "string",
|
|
2457
|
+
"maxLength": 64,
|
|
2458
|
+
"description": "Optional caller-selected run id.",
|
|
2459
|
+
}
|
|
2460
|
+
_SUBAGENT_SPAWN_PARAMETERS["properties"]["depends_on"] = {
|
|
2461
|
+
"type": "array",
|
|
2462
|
+
"items": {"type": "string"},
|
|
2463
|
+
"uniqueItems": True,
|
|
2464
|
+
"description": "Run ids that must succeed first.",
|
|
2465
|
+
}
|
|
2466
|
+
_BUILTIN_TOOL_METADATA = (
|
|
2467
|
+
*_BUILTIN_TOOL_METADATA,
|
|
2468
|
+
BuiltinToolMetadata(
|
|
2469
|
+
name="subagent_spawn",
|
|
2470
|
+
description=(
|
|
2471
|
+
"Start background work; shared is readonly-only, isolated may write, and excess "
|
|
2472
|
+
"queues. Collect with subagent_wait before finalizing; subagent_run is synchronous. "
|
|
2473
|
+
"Example: spawn impl isolated, then verifier with depends_on=[impl] and "
|
|
2474
|
+
"workspace_from_run=impl."
|
|
2475
|
+
),
|
|
2476
|
+
parameters=_SUBAGENT_SPAWN_PARAMETERS,
|
|
2477
|
+
categories=("subagent",),
|
|
2478
|
+
rich=RichToolMetadata(
|
|
2479
|
+
display_name="Spawn Subagent",
|
|
2480
|
+
reasoning_hint="Start independent readonly work without blocking the parent.",
|
|
2481
|
+
action_hint="Spawn a background subagent and retain its run id for collection.",
|
|
2482
|
+
fallback_hint="Use subagent_run when the result is needed immediately.",
|
|
2483
|
+
output_summary_formatter=_summary_subagent_background,
|
|
2484
|
+
),
|
|
2485
|
+
),
|
|
2486
|
+
BuiltinToolMetadata(
|
|
2487
|
+
name="subagent_send",
|
|
2488
|
+
description="Send one message to a queued or running background child.",
|
|
2489
|
+
parameters={
|
|
2490
|
+
"type": "object",
|
|
2491
|
+
"properties": {
|
|
2492
|
+
"run_id": {"type": "string", "description": "Background run id."},
|
|
2493
|
+
"message": {
|
|
2494
|
+
"type": "string",
|
|
2495
|
+
"maxLength": 4000,
|
|
2496
|
+
"description": "Message for the child.",
|
|
2497
|
+
},
|
|
2498
|
+
},
|
|
2499
|
+
"required": ["run_id", "message"],
|
|
2500
|
+
},
|
|
2501
|
+
categories=("subagent",),
|
|
2502
|
+
rich=RichToolMetadata(
|
|
2503
|
+
display_name="Message Subagent",
|
|
2504
|
+
reasoning_hint="Steer useful background work without restarting it.",
|
|
2505
|
+
action_hint="Deliver guidance before the child's next model step.",
|
|
2506
|
+
fallback_hint="Use subagent_status if the child may already be done.",
|
|
2507
|
+
output_summary_formatter=_summary_subagent_background,
|
|
2508
|
+
),
|
|
2509
|
+
),
|
|
2510
|
+
BuiltinToolMetadata(
|
|
2511
|
+
name="subagent_resume",
|
|
2512
|
+
description="Resume a failed, incomplete, or cancelled child as a new linked run.",
|
|
2513
|
+
parameters={
|
|
2514
|
+
"type": "object",
|
|
2515
|
+
"properties": {
|
|
2516
|
+
"run_id": {"type": "string", "description": "Terminal background run id."},
|
|
2517
|
+
"task": {"type": "string", "description": "Optional revised task."},
|
|
2518
|
+
"workspace_view": {
|
|
2519
|
+
"type": "string",
|
|
2520
|
+
"enum": ["shared", "isolated"],
|
|
2521
|
+
"description": ("Optional workspace override for the linked background run."),
|
|
2522
|
+
},
|
|
2523
|
+
"reattach_workspace": {
|
|
2524
|
+
"type": "boolean",
|
|
2525
|
+
"default": True,
|
|
2526
|
+
"description": "Reuse a retained isolated worktree when available.",
|
|
2527
|
+
},
|
|
2528
|
+
},
|
|
2529
|
+
"required": ["run_id"],
|
|
2530
|
+
},
|
|
2531
|
+
categories=("subagent",),
|
|
2532
|
+
rich=RichToolMetadata(
|
|
2533
|
+
display_name="Resume Subagent",
|
|
2534
|
+
reasoning_hint="Continue a terminal child with its prior conversation.",
|
|
2535
|
+
action_hint="Create a fresh linked run with current sandbox limits.",
|
|
2536
|
+
fallback_hint="Successful and degraded runs cannot be resumed.",
|
|
2537
|
+
output_summary_formatter=_summary_subagent_background,
|
|
2538
|
+
),
|
|
2539
|
+
),
|
|
2540
|
+
BuiltinToolMetadata(
|
|
2541
|
+
name="subagent_status",
|
|
2542
|
+
description="List background child state, steps, elapsed time, and activity.",
|
|
2543
|
+
parameters={
|
|
2544
|
+
"type": "object",
|
|
2545
|
+
"properties": {
|
|
2546
|
+
"run_id": {
|
|
2547
|
+
"type": "string",
|
|
2548
|
+
"description": "Optional background run id. Omit to list all runs.",
|
|
2549
|
+
}
|
|
2550
|
+
},
|
|
2551
|
+
},
|
|
2552
|
+
categories=("subagent",),
|
|
2553
|
+
rich=RichToolMetadata(
|
|
2554
|
+
display_name="Subagent Status",
|
|
2555
|
+
reasoning_hint="Check background work without blocking for completion.",
|
|
2556
|
+
action_hint="Read live background child states.",
|
|
2557
|
+
fallback_hint="Use subagent_wait to collect completed reports.",
|
|
2558
|
+
output_summary_formatter=_summary_subagent_background,
|
|
2559
|
+
),
|
|
2560
|
+
),
|
|
2561
|
+
BuiltinToolMetadata(
|
|
2562
|
+
name="subagent_wait",
|
|
2563
|
+
description=(
|
|
2564
|
+
"Collect one or all background results in spawn order. A timeout also returns "
|
|
2565
|
+
"pending run ids; call again before finalizing."
|
|
2566
|
+
),
|
|
2567
|
+
parameters={
|
|
2568
|
+
"type": "object",
|
|
2569
|
+
"properties": {
|
|
2570
|
+
"run_id": {
|
|
2571
|
+
"type": "string",
|
|
2572
|
+
"default": "all",
|
|
2573
|
+
"description": "Background run id or 'all' (default).",
|
|
2574
|
+
},
|
|
2575
|
+
"timeout_s": {
|
|
2576
|
+
"type": "number",
|
|
2577
|
+
"minimum": 0,
|
|
2578
|
+
"maximum": 3600,
|
|
2579
|
+
"description": "Optional maximum seconds to wait before returning pending ids.",
|
|
2580
|
+
},
|
|
2581
|
+
},
|
|
2582
|
+
},
|
|
2583
|
+
categories=("subagent",),
|
|
2584
|
+
rich=RichToolMetadata(
|
|
2585
|
+
display_name="Wait for Subagent",
|
|
2586
|
+
reasoning_hint="Collect background reports before using them or finalizing.",
|
|
2587
|
+
action_hint="Wait for selected background children and return completed results.",
|
|
2588
|
+
fallback_hint="If time expires, continue useful work and wait again later.",
|
|
2589
|
+
output_summary_formatter=_summary_subagent_background,
|
|
2590
|
+
),
|
|
2591
|
+
),
|
|
2592
|
+
BuiltinToolMetadata(
|
|
2593
|
+
name="subagent_cancel",
|
|
2594
|
+
description=(
|
|
2595
|
+
"Cancel one or all background runs. Queued children do not launch; running children "
|
|
2596
|
+
"receive cooperative cancellation."
|
|
2597
|
+
),
|
|
2598
|
+
parameters={
|
|
2599
|
+
"type": "object",
|
|
2600
|
+
"properties": {
|
|
2601
|
+
"run_id": {
|
|
2602
|
+
"type": "string",
|
|
2603
|
+
"default": "all",
|
|
2604
|
+
"description": "Background run id or 'all' (default).",
|
|
2605
|
+
}
|
|
2606
|
+
},
|
|
2607
|
+
},
|
|
2608
|
+
categories=("subagent",),
|
|
2609
|
+
rich=RichToolMetadata(
|
|
2610
|
+
display_name="Cancel Subagent",
|
|
2611
|
+
reasoning_hint="Stop background work that is no longer useful.",
|
|
2612
|
+
action_hint="Cancel queued or running background children.",
|
|
2613
|
+
fallback_hint="Use subagent_status to confirm terminal states.",
|
|
2614
|
+
output_summary_formatter=_summary_subagent_background,
|
|
2615
|
+
),
|
|
2616
|
+
),
|
|
2617
|
+
BuiltinToolMetadata(
|
|
2618
|
+
name="subagent_apply",
|
|
2619
|
+
description=(
|
|
2620
|
+
"Apply one isolated run; non-successful runs require "
|
|
2621
|
+
"acknowledge_incomplete=true and conflicts apply nothing."
|
|
2622
|
+
),
|
|
2623
|
+
parameters={
|
|
2624
|
+
"type": "object",
|
|
2625
|
+
"properties": {
|
|
2626
|
+
"run_id": {"type": "string", "description": "Isolated run id."},
|
|
2627
|
+
"acknowledge_incomplete": {
|
|
2628
|
+
"type": "boolean",
|
|
2629
|
+
"default": False,
|
|
2630
|
+
"description": "Accept a non-successful candidate's unfinished work.",
|
|
2631
|
+
},
|
|
2632
|
+
},
|
|
2633
|
+
"required": ["run_id"],
|
|
2634
|
+
},
|
|
2635
|
+
categories=("subagent", "write", "git"),
|
|
2636
|
+
rich=RichToolMetadata(
|
|
2637
|
+
display_name="Apply Subagent Workspace",
|
|
2638
|
+
reasoning_hint="Bring a reviewed isolated patch into the parent workspace.",
|
|
2639
|
+
action_hint="Check and apply the captured patch without committing it.",
|
|
2640
|
+
fallback_hint="Resolve parent conflicts or discard the isolated run.",
|
|
2641
|
+
),
|
|
2642
|
+
),
|
|
2643
|
+
BuiltinToolMetadata(
|
|
2644
|
+
name="subagent_discard",
|
|
2645
|
+
description="Delete one retained isolated workspace without applying it.",
|
|
2646
|
+
parameters={
|
|
2647
|
+
"type": "object",
|
|
2648
|
+
"properties": {"run_id": {"type": "string", "description": "Isolated run id."}},
|
|
2649
|
+
"required": ["run_id"],
|
|
2650
|
+
},
|
|
2651
|
+
categories=("subagent",),
|
|
2652
|
+
rich=RichToolMetadata(
|
|
2653
|
+
display_name="Discard Subagent Workspace",
|
|
2654
|
+
reasoning_hint="Release isolated work that should not enter the parent workspace.",
|
|
2655
|
+
action_hint="Delete the retained worktree and mark it discarded.",
|
|
2656
|
+
fallback_hint="Use subagent_apply when the captured patch should be kept.",
|
|
2657
|
+
),
|
|
2658
|
+
),
|
|
2659
|
+
)
|
|
2660
|
+
|
|
2661
|
+
_BUILTIN_TOOL_METADATA_BY_NAME = {spec.name: spec for spec in _BUILTIN_TOOL_METADATA}
|
|
2662
|
+
if len(_BUILTIN_TOOL_METADATA_BY_NAME) != len(_BUILTIN_TOOL_METADATA):
|
|
2663
|
+
raise RuntimeError("Duplicate built-in tool name detected in registry.")
|
|
2664
|
+
|
|
2665
|
+
|
|
2666
|
+
def iter_builtin_tool_metadata() -> tuple[BuiltinToolMetadata, ...]:
|
|
2667
|
+
return _BUILTIN_TOOL_METADATA
|
|
2668
|
+
|
|
2669
|
+
|
|
2670
|
+
def get_builtin_tool_metadata(name: str) -> BuiltinToolMetadata | None:
|
|
2671
|
+
return _BUILTIN_TOOL_METADATA_BY_NAME.get(name)
|
|
2672
|
+
|
|
2673
|
+
|
|
2674
|
+
def require_builtin_tool_metadata(name: str) -> BuiltinToolMetadata:
|
|
2675
|
+
spec = get_builtin_tool_metadata(name)
|
|
2676
|
+
if spec is None:
|
|
2677
|
+
raise KeyError(f"Unknown built-in tool: {name}")
|
|
2678
|
+
return spec
|
|
2679
|
+
|
|
2680
|
+
|
|
2681
|
+
def copied_tool_parameters(name: str) -> dict[str, Any]:
|
|
2682
|
+
return require_builtin_tool_metadata(name).copied_parameters()
|
|
2683
|
+
|
|
2684
|
+
|
|
2685
|
+
def builtin_tool_names_with_category(category: str) -> tuple[str, ...]:
|
|
2686
|
+
normalized = str(category or "").strip().lower()
|
|
2687
|
+
return tuple(
|
|
2688
|
+
spec.name
|
|
2689
|
+
for spec in _BUILTIN_TOOL_METADATA
|
|
2690
|
+
if normalized in {tag.lower() for tag in spec.categories}
|
|
2691
|
+
)
|
|
2692
|
+
|
|
2693
|
+
|
|
2694
|
+
def built_in_subagent_tool_names(*, exposure: str = "readonly") -> tuple[str, ...]:
|
|
2695
|
+
normalized = str(exposure or "").strip().lower()
|
|
2696
|
+
return tuple(
|
|
2697
|
+
spec.name
|
|
2698
|
+
for spec in _BUILTIN_TOOL_METADATA
|
|
2699
|
+
if spec.built_in_subagent_exposure.strip().lower() == normalized
|
|
2700
|
+
)
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
def tool_display_name(tool_name: str) -> str:
|
|
2704
|
+
spec = get_builtin_tool_metadata(tool_name)
|
|
2705
|
+
if spec is None:
|
|
2706
|
+
return tool_name
|
|
2707
|
+
return spec.rich.display_name
|
|
2708
|
+
|
|
2709
|
+
|
|
2710
|
+
def tool_input_preview(tool_name: str, args: dict[str, Any]) -> str:
|
|
2711
|
+
spec = get_builtin_tool_metadata(tool_name)
|
|
2712
|
+
if spec is None or spec.rich.input_preview_formatter is None:
|
|
2713
|
+
return "-"
|
|
2714
|
+
return spec.rich.input_preview_formatter(args)
|
|
2715
|
+
|
|
2716
|
+
|
|
2717
|
+
@dataclass(frozen=True)
|
|
2718
|
+
class CompatibilityToolAlias:
|
|
2719
|
+
alias: str
|
|
2720
|
+
target: str
|
|
2721
|
+
transform: ToolAliasTransform
|
|
2722
|
+
description: str
|
|
2723
|
+
|
|
2724
|
+
|
|
2725
|
+
def _identity_alias_transform(args: dict[str, Any]) -> dict[str, Any]:
|
|
2726
|
+
return copy.deepcopy(args)
|
|
2727
|
+
|
|
2728
|
+
|
|
2729
|
+
_COMPATIBILITY_TOOL_ALIASES: dict[str, CompatibilityToolAlias] = {
|
|
2730
|
+
"read_file": CompatibilityToolAlias(
|
|
2731
|
+
alias="read_file",
|
|
2732
|
+
target="fs_read",
|
|
2733
|
+
transform=_identity_alias_transform,
|
|
2734
|
+
description="read_file is a schema-compatible alias for fs_read.",
|
|
2735
|
+
),
|
|
2736
|
+
}
|
|
2737
|
+
_AMBIGUOUS_TOOL_ALIASES: dict[str, tuple[str, ...]] = {
|
|
2738
|
+
"read": ("fs_read", "fs_read_lines", "web_fetch"),
|
|
2739
|
+
"write": ("fs_write", "fs_edit", "shell_run"),
|
|
2740
|
+
"search": ("search_rg", "web_search"),
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
|
|
2744
|
+
def build_unknown_tool_recovery_payload(
|
|
2745
|
+
*,
|
|
2746
|
+
requested_tool_name: str,
|
|
2747
|
+
arguments: dict[str, Any],
|
|
2748
|
+
available_tool_names: Collection[str],
|
|
2749
|
+
) -> dict[str, Any]:
|
|
2750
|
+
requested = str(requested_tool_name or "").strip()
|
|
2751
|
+
available = _safe_available_tool_names(available_tool_names)
|
|
2752
|
+
alias = compatibility_tool_alias_for(
|
|
2753
|
+
requested_tool_name=requested,
|
|
2754
|
+
arguments=arguments,
|
|
2755
|
+
available_tool_names=available,
|
|
2756
|
+
)
|
|
2757
|
+
ambiguous_targets = _AMBIGUOUS_TOOL_ALIASES.get(_normalize_tool_alias_name(requested), ())
|
|
2758
|
+
suggestions = nearest_tool_name_suggestions(
|
|
2759
|
+
requested_tool_name=requested,
|
|
2760
|
+
available_tool_names=available,
|
|
2761
|
+
)
|
|
2762
|
+
guidance = _unknown_tool_guidance(
|
|
2763
|
+
requested=requested,
|
|
2764
|
+
available=available,
|
|
2765
|
+
suggestions=suggestions,
|
|
2766
|
+
alias=alias,
|
|
2767
|
+
ambiguous_targets=ambiguous_targets,
|
|
2768
|
+
)
|
|
2769
|
+
payload: dict[str, Any] = {
|
|
2770
|
+
"error": f"Unknown tool: {requested}",
|
|
2771
|
+
"error_code": "unknown_tool",
|
|
2772
|
+
"requested_tool_name": requested,
|
|
2773
|
+
"available_tool_names": available,
|
|
2774
|
+
"nearest_tool_suggestions": suggestions,
|
|
2775
|
+
"safe_compatibility_alias": alias is not None,
|
|
2776
|
+
"guidance": guidance,
|
|
2777
|
+
}
|
|
2778
|
+
if alias is not None:
|
|
2779
|
+
payload["compatibility_alias"] = {
|
|
2780
|
+
"alias": alias.alias,
|
|
2781
|
+
"target": alias.target,
|
|
2782
|
+
"description": alias.description,
|
|
2783
|
+
}
|
|
2784
|
+
if ambiguous_targets:
|
|
2785
|
+
payload["ambiguous_alias_targets"] = [
|
|
2786
|
+
target for target in ambiguous_targets if target in available
|
|
2787
|
+
]
|
|
2788
|
+
payload["alias_ambiguous"] = True
|
|
2789
|
+
return payload
|
|
2790
|
+
|
|
2791
|
+
|
|
2792
|
+
def compatibility_tool_alias_for(
|
|
2793
|
+
*,
|
|
2794
|
+
requested_tool_name: str,
|
|
2795
|
+
arguments: dict[str, Any],
|
|
2796
|
+
available_tool_names: Collection[str],
|
|
2797
|
+
) -> CompatibilityToolAlias | None:
|
|
2798
|
+
normalized = _normalize_tool_alias_name(requested_tool_name)
|
|
2799
|
+
alias = _COMPATIBILITY_TOOL_ALIASES.get(normalized)
|
|
2800
|
+
if alias is None:
|
|
2801
|
+
return None
|
|
2802
|
+
available = set(_safe_available_tool_names(available_tool_names))
|
|
2803
|
+
if alias.target not in available:
|
|
2804
|
+
return None
|
|
2805
|
+
try:
|
|
2806
|
+
transformed = alias.transform(arguments)
|
|
2807
|
+
except Exception: # noqa: BLE001 - bad alias input should stay non-executing.
|
|
2808
|
+
return None
|
|
2809
|
+
if not isinstance(transformed, dict):
|
|
2810
|
+
return None
|
|
2811
|
+
return alias
|
|
2812
|
+
|
|
2813
|
+
|
|
2814
|
+
def transform_compatibility_tool_alias(
|
|
2815
|
+
alias: CompatibilityToolAlias,
|
|
2816
|
+
arguments: dict[str, Any],
|
|
2817
|
+
) -> dict[str, Any]:
|
|
2818
|
+
transformed = alias.transform(arguments)
|
|
2819
|
+
if not isinstance(transformed, dict):
|
|
2820
|
+
raise ValueError(f"Compatibility alias {alias.alias!r} did not return arguments.")
|
|
2821
|
+
return transformed
|
|
2822
|
+
|
|
2823
|
+
|
|
2824
|
+
def nearest_tool_name_suggestions(
|
|
2825
|
+
*,
|
|
2826
|
+
requested_tool_name: str,
|
|
2827
|
+
available_tool_names: Collection[str],
|
|
2828
|
+
) -> list[str]:
|
|
2829
|
+
requested = _normalize_tool_alias_name(requested_tool_name)
|
|
2830
|
+
scored: list[tuple[float, str]] = []
|
|
2831
|
+
for name in _safe_available_tool_names(available_tool_names):
|
|
2832
|
+
normalized = _normalize_tool_alias_name(name)
|
|
2833
|
+
if not requested or not normalized:
|
|
2834
|
+
continue
|
|
2835
|
+
score = SequenceMatcher(a=requested, b=normalized).ratio()
|
|
2836
|
+
if requested in normalized or normalized in requested:
|
|
2837
|
+
score = max(score, 0.8)
|
|
2838
|
+
if score >= _UNKNOWN_TOOL_SUGGESTION_THRESHOLD:
|
|
2839
|
+
scored.append((score, name))
|
|
2840
|
+
scored.sort(key=lambda item: (-item[0], item[1]))
|
|
2841
|
+
return [name for _score, name in scored[:_UNKNOWN_TOOL_MAX_SUGGESTIONS]]
|
|
2842
|
+
|
|
2843
|
+
|
|
2844
|
+
def _safe_available_tool_names(available_tool_names: Collection[str]) -> list[str]:
|
|
2845
|
+
return sorted(
|
|
2846
|
+
{str(name or "").strip() for name in available_tool_names if str(name or "").strip()}
|
|
2847
|
+
)
|
|
2848
|
+
|
|
2849
|
+
|
|
2850
|
+
def _normalize_tool_alias_name(name: str) -> str:
|
|
2851
|
+
return re.sub(r"[^a-z0-9]+", "_", str(name or "").strip().casefold()).strip("_")
|
|
2852
|
+
|
|
2853
|
+
|
|
2854
|
+
def _unknown_tool_guidance(
|
|
2855
|
+
*,
|
|
2856
|
+
requested: str,
|
|
2857
|
+
available: list[str],
|
|
2858
|
+
suggestions: list[str],
|
|
2859
|
+
alias: CompatibilityToolAlias | None,
|
|
2860
|
+
ambiguous_targets: tuple[str, ...],
|
|
2861
|
+
) -> str:
|
|
2862
|
+
if alias is not None:
|
|
2863
|
+
return (
|
|
2864
|
+
f"Tool {requested!r} is not registered, but a reviewed compatibility alias "
|
|
2865
|
+
f"can run {alias.target!r} with the same argument schema."
|
|
2866
|
+
)
|
|
2867
|
+
if ambiguous_targets:
|
|
2868
|
+
valid_targets = [target for target in ambiguous_targets if target in available]
|
|
2869
|
+
target_text = ", ".join(valid_targets) if valid_targets else ", ".join(ambiguous_targets)
|
|
2870
|
+
return (
|
|
2871
|
+
f"Tool {requested!r} is ambiguous. Retry with one exact available tool name "
|
|
2872
|
+
f"and its schema; possible targets include: {target_text}."
|
|
2873
|
+
)
|
|
2874
|
+
if suggestions:
|
|
2875
|
+
return (
|
|
2876
|
+
f"Tool {requested!r} is unavailable. Retry with one available tool schema; "
|
|
2877
|
+
f"nearest valid name: {suggestions[0]}."
|
|
2878
|
+
)
|
|
2879
|
+
preview = ", ".join(available[:8])
|
|
2880
|
+
if len(available) > 8:
|
|
2881
|
+
preview += ", ..."
|
|
2882
|
+
return (
|
|
2883
|
+
f"Tool {requested!r} is unavailable. Retry with one exact available tool name "
|
|
2884
|
+
f"and required arguments. Available tools: {preview or '(none)'}."
|
|
2885
|
+
)
|
|
2886
|
+
|
|
2887
|
+
|
|
2888
|
+
def summarize_tool_output_chunk(tool_name: str, chunk: str) -> str:
|
|
2889
|
+
try:
|
|
2890
|
+
parsed = json.loads(chunk)
|
|
2891
|
+
except json.JSONDecodeError:
|
|
2892
|
+
return _truncate_inline(chunk, max_chars=180)
|
|
2893
|
+
|
|
2894
|
+
if not isinstance(parsed, dict):
|
|
2895
|
+
compact = json.dumps(parsed, ensure_ascii=True)
|
|
2896
|
+
return _truncate_inline(compact, max_chars=180)
|
|
2897
|
+
|
|
2898
|
+
summary_text = _truncate_inline(str(parsed.get("summary") or ""), max_chars=160)
|
|
2899
|
+
preview = _truncate_inline(str(parsed.get("preview") or ""), max_chars=96)
|
|
2900
|
+
|
|
2901
|
+
if is_tool_unavailable_result(parsed):
|
|
2902
|
+
tool = _truncate_inline(parsed["tool"], max_chars=64)
|
|
2903
|
+
reason = _truncate_inline(parsed["reason"], max_chars=140)
|
|
2904
|
+
return f"Tool unavailable: {tool}: {reason}"
|
|
2905
|
+
|
|
2906
|
+
if parsed.get("transcript_shaped") is True:
|
|
2907
|
+
if summary_text:
|
|
2908
|
+
return summary_text + (f" Preview: {preview}" if preview else "")
|
|
2909
|
+
if preview:
|
|
2910
|
+
return f"Transcript retained a bounded preview. Preview: {preview}"
|
|
2911
|
+
return "Tool output was summarized for transcript retention."
|
|
2912
|
+
|
|
2913
|
+
if parsed.get("offloaded") is True:
|
|
2914
|
+
artifact_ref = str(
|
|
2915
|
+
parsed.get("artifact_locator") or parsed.get("artifact_path") or ""
|
|
2916
|
+
).strip()
|
|
2917
|
+
if artifact_ref:
|
|
2918
|
+
if summary_text:
|
|
2919
|
+
return f"{summary_text} Artifact: {artifact_ref}." + (
|
|
2920
|
+
f" Preview: {preview}" if preview else ""
|
|
2921
|
+
)
|
|
2922
|
+
return f"Output offloaded to {artifact_ref}." + (
|
|
2923
|
+
f" Preview: {preview}" if preview else ""
|
|
2924
|
+
)
|
|
2925
|
+
if summary_text:
|
|
2926
|
+
return summary_text + (f" Preview: {preview}" if preview else "")
|
|
2927
|
+
return "Output offloaded into session artifacts."
|
|
2928
|
+
|
|
2929
|
+
spec = get_builtin_tool_metadata(tool_name)
|
|
2930
|
+
if tool_name != "subagent_run" and "error" in parsed:
|
|
2931
|
+
msg = _truncate_inline(str(parsed.get("error") or ""), max_chars=140)
|
|
2932
|
+
return f"Error: {msg}"
|
|
2933
|
+
if spec is not None and spec.rich.output_summary_formatter is not None:
|
|
2934
|
+
return spec.rich.output_summary_formatter(parsed)
|
|
2935
|
+
keys = ", ".join(sorted(str(k) for k in parsed.keys())[:6])
|
|
2936
|
+
return f"Output keys: {keys or '-'}."
|