langchain-agentx-python 0.1__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.
- langchain_agentx/__init__.py +46 -0
- langchain_agentx/command/__init__.py +28 -0
- langchain_agentx/command/builtin/__init__.py +25 -0
- langchain_agentx/command/builtin/clear.py +33 -0
- langchain_agentx/command/builtin/compact.py +33 -0
- langchain_agentx/command/builtin/memory.py +37 -0
- langchain_agentx/command/builtin/reload_plugins.py +42 -0
- langchain_agentx/command/context.py +30 -0
- langchain_agentx/command/dispatcher.py +183 -0
- langchain_agentx/command/registry.py +110 -0
- langchain_agentx/command/result.py +25 -0
- langchain_agentx/command/types.py +41 -0
- langchain_agentx/config/__init__.py +14 -0
- langchain_agentx/loop/__init__.py +47 -0
- langchain_agentx/loop/config/__init__.py +20 -0
- langchain_agentx/loop/config/agent_config.py +66 -0
- langchain_agentx/loop/config/agent_loop_config.py +72 -0
- langchain_agentx/loop/config/model_context_resolver.py +105 -0
- langchain_agentx/loop/config/runtime_settings.py +50 -0
- langchain_agentx/loop/config/token_estimator.py +133 -0
- langchain_agentx/loop/context/__init__.py +66 -0
- langchain_agentx/loop/context/blocking_guard.py +97 -0
- langchain_agentx/loop/context/compaction_service.py +60 -0
- langchain_agentx/loop/context/message_utils.py +56 -0
- langchain_agentx/loop/context/pipeline.py +127 -0
- langchain_agentx/loop/context/settings.py +103 -0
- langchain_agentx/loop/context/stages/__init__.py +29 -0
- langchain_agentx/loop/context/stages/autocompact.py +140 -0
- langchain_agentx/loop/context/stages/base.py +32 -0
- langchain_agentx/loop/context/stages/collapse.py +76 -0
- langchain_agentx/loop/context/stages/microcompact.py +76 -0
- langchain_agentx/loop/context/stages/noop.py +33 -0
- langchain_agentx/loop/context/stages/snip.py +71 -0
- langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
- langchain_agentx/loop/context/types.py +79 -0
- langchain_agentx/loop/exit/__init__.py +1 -0
- langchain_agentx/loop/exit/exit_logic.py +320 -0
- langchain_agentx/loop/exit/reason_codes.py +39 -0
- langchain_agentx/loop/graph/__init__.py +5 -0
- langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
- langchain_agentx/loop/graph/factory.py +1409 -0
- langchain_agentx/loop/graph/graph_edges.py +820 -0
- langchain_agentx/loop/hook/__init__.py +48 -0
- langchain_agentx/loop/hook/async_hook_runner.py +62 -0
- langchain_agentx/loop/hook/config.py +280 -0
- langchain_agentx/loop/hook/engine.py +321 -0
- langchain_agentx/loop/hook/executors/__init__.py +9 -0
- langchain_agentx/loop/hook/executors/agent.py +107 -0
- langchain_agentx/loop/hook/executors/command.py +230 -0
- langchain_agentx/loop/hook/executors/http.py +114 -0
- langchain_agentx/loop/hook/executors/prompt.py +92 -0
- langchain_agentx/loop/hook/graph_wiring.py +134 -0
- langchain_agentx/loop/hook/registry.py +262 -0
- langchain_agentx/loop/hook/trust.py +43 -0
- langchain_agentx/loop/hook/types.py +110 -0
- langchain_agentx/loop/injection/__init__.py +13 -0
- langchain_agentx/loop/injection/dedup.py +74 -0
- langchain_agentx/loop/loop_abort.py +36 -0
- langchain_agentx/loop/model/__init__.py +1 -0
- langchain_agentx/loop/model/model_node.py +648 -0
- langchain_agentx/loop/model/model_nodes.py +661 -0
- langchain_agentx/loop/model/orphan_tool_results.py +38 -0
- langchain_agentx/loop/model/retrier.py +307 -0
- langchain_agentx/loop/model/retry_bridge.py +58 -0
- langchain_agentx/loop/model/retry_events.py +35 -0
- langchain_agentx/loop/model/retry_policy.py +56 -0
- langchain_agentx/loop/model/schema_and_format.py +153 -0
- langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
- langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
- langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
- langchain_agentx/loop/prompt/__init__.py +95 -0
- langchain_agentx/loop/prompt/builder.py +61 -0
- langchain_agentx/loop/prompt/builtin.py +218 -0
- langchain_agentx/loop/prompt/compact.py +408 -0
- langchain_agentx/loop/prompt/sections.py +120 -0
- langchain_agentx/loop/runtime/__init__.py +19 -0
- langchain_agentx/loop/runtime/context.py +34 -0
- langchain_agentx/loop/runtime/context_factory.py +107 -0
- langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
- langchain_agentx/loop/subagent/__init__.py +53 -0
- langchain_agentx/loop/subagent/async_runner.py +215 -0
- langchain_agentx/loop/subagent/context.py +209 -0
- langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
- langchain_agentx/loop/subagent/graph.py +72 -0
- langchain_agentx/loop/subagent/orchestrator.py +391 -0
- langchain_agentx/loop/subagent/progress.py +30 -0
- langchain_agentx/loop/subagent/prompt.py +52 -0
- langchain_agentx/loop/subagent/runner.py +504 -0
- langchain_agentx/loop/subagent/transcript.py +172 -0
- langchain_agentx/memory/__init__.py +2 -0
- langchain_agentx/memory/instruction/__init__.py +12 -0
- langchain_agentx/memory/instruction/loader.py +325 -0
- langchain_agentx/memory/instruction/resolver.py +24 -0
- langchain_agentx/memory/instruction/runtime.py +83 -0
- langchain_agentx/memory/instruction/sections.py +83 -0
- langchain_agentx/memory/instruction/types.py +59 -0
- langchain_agentx/memory/memdir/__init__.py +77 -0
- langchain_agentx/memory/memdir/age.py +36 -0
- langchain_agentx/memory/memdir/agent_memory.py +380 -0
- langchain_agentx/memory/memdir/extractor.py +309 -0
- langchain_agentx/memory/memdir/loader.py +187 -0
- langchain_agentx/memory/memdir/paths.py +63 -0
- langchain_agentx/memory/memdir/recall.py +45 -0
- langchain_agentx/memory/memdir/runtime.py +43 -0
- langchain_agentx/memory/memdir/scan.py +135 -0
- langchain_agentx/memory/memdir/types.py +104 -0
- langchain_agentx/memory/session/__init__.py +76 -0
- langchain_agentx/memory/session/compact_bridge.py +208 -0
- langchain_agentx/memory/session/prompts.py +172 -0
- langchain_agentx/memory/session/session_memory.py +282 -0
- langchain_agentx/observability/__init__.py +67 -0
- langchain_agentx/observability/evaluation/__init__.py +17 -0
- langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
- langchain_agentx/observability/evaluation/checkers/base.py +34 -0
- langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
- langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
- langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
- langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
- langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
- langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
- langchain_agentx/observability/evaluation/service.py +102 -0
- langchain_agentx/observability/evaluation/state.py +32 -0
- langchain_agentx/observability/evaluation/store.py +258 -0
- langchain_agentx/observability/events/__init__.py +15 -0
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
- langchain_agentx/observability/logging/__init__.py +15 -0
- langchain_agentx/observability/logging/debug_burst.py +95 -0
- langchain_agentx/observability/logging/logging_config.py +178 -0
- langchain_agentx/observability/logging/logging_contract.py +65 -0
- langchain_agentx/observability/replay/__init__.py +35 -0
- langchain_agentx/observability/replay/cli.py +91 -0
- langchain_agentx/observability/replay/service.py +83 -0
- langchain_agentx/observability/replay/store.py +278 -0
- langchain_agentx/observability/replay/ui.py +47 -0
- langchain_agentx/observability/trace/__init__.py +25 -0
- langchain_agentx/observability/trace/collector.py +560 -0
- langchain_agentx/observability/trace/event_emitter.py +183 -0
- langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
- langchain_agentx/observability/trace/models.py +144 -0
- langchain_agentx/observability/trace/sqlite_store.py +873 -0
- langchain_agentx/observability/trace/trace_callback.py +295 -0
- langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
- langchain_agentx/plugin/__init__.py +26 -0
- langchain_agentx/plugin/builtin.py +53 -0
- langchain_agentx/plugin/config.py +113 -0
- langchain_agentx/plugin/loader.py +386 -0
- langchain_agentx/plugin/manifest.py +154 -0
- langchain_agentx/plugin/registries.py +211 -0
- langchain_agentx/plugin/types.py +142 -0
- langchain_agentx/provider/__init__.py +27 -0
- langchain_agentx/provider/anthropic.py +121 -0
- langchain_agentx/provider/compatible_chat_openai.py +86 -0
- langchain_agentx/provider/env.py +45 -0
- langchain_agentx/provider/model_profile.py +156 -0
- langchain_agentx/provider/openai.py +89 -0
- langchain_agentx/session/__init__.py +17 -0
- langchain_agentx/session/agent_session.py +320 -0
- langchain_agentx/session/conversation_factory.py +87 -0
- langchain_agentx/session/conversation_recovery.py +156 -0
- langchain_agentx/session/conversation_session.py +198 -0
- langchain_agentx/session/factory.py +143 -0
- langchain_agentx/session/protocol.py +25 -0
- langchain_agentx/task_runtime/__init__.py +113 -0
- langchain_agentx/task_runtime/core/__init__.py +51 -0
- langchain_agentx/task_runtime/core/ids.py +33 -0
- langchain_agentx/task_runtime/core/interfaces.py +115 -0
- langchain_agentx/task_runtime/core/notification_priority.py +19 -0
- langchain_agentx/task_runtime/core/types.py +136 -0
- langchain_agentx/task_runtime/integrations/__init__.py +33 -0
- langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
- langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
- langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
- langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
- langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
- langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
- langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
- langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
- langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
- langchain_agentx/task_runtime/output/__init__.py +5 -0
- langchain_agentx/task_runtime/output/sink.py +64 -0
- langchain_agentx/task_runtime/policy/__init__.py +11 -0
- langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
- langchain_agentx/task_runtime/queue/__init__.py +5 -0
- langchain_agentx/task_runtime/queue/in_memory.py +55 -0
- langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
- langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
- langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
- langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
- langchain_agentx/task_runtime/store/__init__.py +6 -0
- langchain_agentx/task_runtime/store/in_memory.py +81 -0
- langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
- langchain_agentx/task_runtime/tasks/__init__.py +76 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
- langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
- langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
- langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
- langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
- langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
- langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
- langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
- langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
- langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
- langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
- langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
- langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
- langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
- langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
- langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
- langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
- langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
- langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
- langchain_agentx/tool_runtime/__init__.py +90 -0
- langchain_agentx/tool_runtime/adapter.py +365 -0
- langchain_agentx/tool_runtime/base.py +319 -0
- langchain_agentx/tool_runtime/errors.py +190 -0
- langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
- langchain_agentx/tool_runtime/loader.py +195 -0
- langchain_agentx/tool_runtime/models.py +260 -0
- langchain_agentx/tool_runtime/permission_context.py +78 -0
- langchain_agentx/tool_runtime/pipeline.py +621 -0
- langchain_agentx/tool_runtime/policy.py +447 -0
- langchain_agentx/tool_runtime/registry.py +81 -0
- langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
- langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
- langchain_agentx/tool_runtime/resolvers/background.py +32 -0
- langchain_agentx/tool_runtime/resolvers/base.py +20 -0
- langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
- langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
- langchain_agentx/tool_runtime/session_store.py +132 -0
- langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
- langchain_agentx/tool_runtime/state_bridge.py +164 -0
- langchain_agentx/tools/__init__.py +26 -0
- langchain_agentx/tools/agent/__init__.py +9 -0
- langchain_agentx/tools/agent/backend.py +53 -0
- langchain_agentx/tools/agent/built_in/__init__.py +19 -0
- langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
- langchain_agentx/tools/agent/built_in/explore.py +80 -0
- langchain_agentx/tools/agent/built_in/general.py +57 -0
- langchain_agentx/tools/agent/built_in/plan.py +89 -0
- langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
- langchain_agentx/tools/agent/built_in/verification.py +120 -0
- langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
- langchain_agentx/tools/agent/cwd_resolution.py +119 -0
- langchain_agentx/tools/agent/limits.py +26 -0
- langchain_agentx/tools/agent/loader.py +270 -0
- langchain_agentx/tools/agent/models.py +85 -0
- langchain_agentx/tools/agent/prompt.py +120 -0
- langchain_agentx/tools/agent/registry/__init__.py +18 -0
- langchain_agentx/tools/agent/registry/config.py +29 -0
- langchain_agentx/tools/agent/registry/registry.py +47 -0
- langchain_agentx/tools/agent/scope.py +137 -0
- langchain_agentx/tools/agent/tool.py +256 -0
- langchain_agentx/tools/bash/__init__.py +9 -0
- langchain_agentx/tools/bash/ast_security.py +571 -0
- langchain_agentx/tools/bash/backend.py +1447 -0
- langchain_agentx/tools/bash/bash_hardening.py +734 -0
- langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
- langchain_agentx/tools/bash/cwd_reporter.py +95 -0
- langchain_agentx/tools/bash/limits.py +71 -0
- langchain_agentx/tools/bash/mode_validation.py +282 -0
- langchain_agentx/tools/bash/models.py +131 -0
- langchain_agentx/tools/bash/observability.py +148 -0
- langchain_agentx/tools/bash/output_utils.py +200 -0
- langchain_agentx/tools/bash/path_security.py +2429 -0
- langchain_agentx/tools/bash/prompt.py +68 -0
- langchain_agentx/tools/bash/read_only_validation.py +589 -0
- langchain_agentx/tools/bash/result_presenter.py +324 -0
- langchain_agentx/tools/bash/sandbox_decision.py +133 -0
- langchain_agentx/tools/bash/security.py +311 -0
- langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
- langchain_agentx/tools/bash/sed_validation.py +163 -0
- langchain_agentx/tools/bash/semantics.py +111 -0
- langchain_agentx/tools/bash/session_manager.py +205 -0
- langchain_agentx/tools/bash/session_runtime.py +290 -0
- langchain_agentx/tools/bash/shell_locator.py +191 -0
- langchain_agentx/tools/bash/task_runtime.py +91 -0
- langchain_agentx/tools/bash/tool.py +939 -0
- langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
- langchain_agentx/tools/glob/__init__.py +9 -0
- langchain_agentx/tools/glob/models.py +57 -0
- langchain_agentx/tools/glob/pagination.py +30 -0
- langchain_agentx/tools/glob/prompt.py +24 -0
- langchain_agentx/tools/glob/rg_list_backend.py +139 -0
- langchain_agentx/tools/glob/rg_pattern.py +44 -0
- langchain_agentx/tools/glob/tool.py +327 -0
- langchain_agentx/tools/grep/__init__.py +7 -0
- langchain_agentx/tools/grep/backend.py +375 -0
- langchain_agentx/tools/grep/models.py +127 -0
- langchain_agentx/tools/grep/prompt.py +30 -0
- langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
- langchain_agentx/tools/grep/tool.py +475 -0
- langchain_agentx/tools/read/__init__.py +9 -0
- langchain_agentx/tools/read/backend.py +415 -0
- langchain_agentx/tools/read/limits.py +67 -0
- langchain_agentx/tools/read/models.py +156 -0
- langchain_agentx/tools/read/prompt.py +73 -0
- langchain_agentx/tools/read/tool.py +494 -0
- langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
- langchain_agentx/tools/skill/__init__.py +4 -0
- langchain_agentx/tools/skill/argument_substitution.py +80 -0
- langchain_agentx/tools/skill/loader.py +196 -0
- langchain_agentx/tools/skill/models.py +88 -0
- langchain_agentx/tools/skill/policy.py +80 -0
- langchain_agentx/tools/skill/prompt.py +35 -0
- langchain_agentx/tools/skill/tool.py +222 -0
- langchain_agentx/utils/__init__.py +0 -0
- langchain_agentx/utils/cwd.py +124 -0
- langchain_agentx/utils/host_platform.py +112 -0
- langchain_agentx/utils/path_hierarchy.py +48 -0
- langchain_agentx/utils/path_user_input.py +66 -0
- langchain_agentx/utils/rg_executable.py +18 -0
- langchain_agentx/utils/subprocess_text.py +101 -0
- langchain_agentx/utils/temp_paths.py +77 -0
- langchain_agentx/utils/unc_path.py +25 -0
- langchain_agentx/utils/win_reserved_paths.py +51 -0
- langchain_agentx/workflow/__init__.py +7 -0
- langchain_agentx/workflow/base.py +97 -0
- langchain_agentx/workflow/batch.py +55 -0
- langchain_agentx/workflow/dag.py +54 -0
- langchain_agentx/workspace/__init__.py +13 -0
- langchain_agentx/workspace/config.py +140 -0
- langchain_agentx/workspace/path_key_normalizer.py +30 -0
- langchain_agentx/workspace/resolver.py +74 -0
- langchain_agentx/workspace/validators.py +41 -0
- langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
- langchain_agentx_python-0.1.dist-info/METADATA +513 -0
- langchain_agentx_python-0.1.dist-info/RECORD +354 -0
- langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
- langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
loop/subagent/runner.py — 子 Agent 同步执行路径
|
|
3
|
+
|
|
4
|
+
职责:run_subagent(),plain/fork 模式同步阻塞执行,astream_events 流式处理
|
|
5
|
+
在整体链路中的位置:AgentRuntimeTool.invoke() → run_subagent() → SubagentResult
|
|
6
|
+
CC 对照:runAgent.ts runAgent();onProgress 回调逻辑
|
|
7
|
+
当前裁剪范围:v1 plain/fork 同步路径;coordinate 异步路径见 async_runner.py
|
|
8
|
+
|
|
9
|
+
执行流程(run_subagent):
|
|
10
|
+
1. 校验:is_async=True 时抛 RuntimeError,model=None 时抛 ValueError
|
|
11
|
+
2. 构造 SubagentTranscriptManager(InMemoryTranscriptStore)
|
|
12
|
+
3. create_subagent_graph(ctx, tools, model) 创建子图
|
|
13
|
+
4. 将 ctx.initial_messages 转为 LangChain HumanMessage 列表作为图输入
|
|
14
|
+
5. astream_events 循环(单线收集,CC agentMessages 累积模式):
|
|
15
|
+
on_tool_start → on_progress(tool_use)
|
|
16
|
+
on_tool_end → on_progress(tool_result) + transcript.append()
|
|
17
|
+
on_chat_model_end → 累积 assistant 消息 + total_steps 计数 + on_progress(model_step)
|
|
18
|
+
on_chain_end(LangGraph) → 仅提取 terminal_reason(不再取 messages)
|
|
19
|
+
6. 从累积的 assistant 消息中提取最终内容(CC finalizeAgentTool 语义)
|
|
20
|
+
7. on_progress(completed),返回 SubagentResult
|
|
21
|
+
try/finally:确保 transcript.cleanup() + cleanup_subagent_context() 必然执行
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import asyncio
|
|
27
|
+
import logging
|
|
28
|
+
import threading
|
|
29
|
+
from concurrent.futures import Future
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from typing import Any, Callable
|
|
32
|
+
|
|
33
|
+
from langchain_core.messages import HumanMessage
|
|
34
|
+
|
|
35
|
+
from langchain_agentx.loop.subagent.context import (
|
|
36
|
+
SubagentContext,
|
|
37
|
+
SubagentResult,
|
|
38
|
+
cleanup_subagent_context,
|
|
39
|
+
)
|
|
40
|
+
from langchain_agentx.loop.subagent.graph import create_subagent_graph
|
|
41
|
+
from langchain_agentx.loop.subagent.progress import SubagentProgressEvent
|
|
42
|
+
from langchain_agentx.loop.subagent.transcript import (
|
|
43
|
+
InMemoryTranscriptStore,
|
|
44
|
+
SubagentTranscriptManager,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _run_coroutine_sync(coro: Any) -> Any:
|
|
51
|
+
"""在同步上下文运行协程;若当前已有事件循环则转到子线程执行。"""
|
|
52
|
+
try:
|
|
53
|
+
asyncio.get_running_loop()
|
|
54
|
+
except RuntimeError:
|
|
55
|
+
return asyncio.run(coro)
|
|
56
|
+
|
|
57
|
+
fut: Future[Any] = Future()
|
|
58
|
+
|
|
59
|
+
def _runner() -> None:
|
|
60
|
+
try:
|
|
61
|
+
fut.set_result(asyncio.run(coro))
|
|
62
|
+
except Exception as exc:
|
|
63
|
+
fut.set_exception(exc)
|
|
64
|
+
|
|
65
|
+
thread = threading.Thread(target=_runner, daemon=True)
|
|
66
|
+
thread.start()
|
|
67
|
+
thread.join()
|
|
68
|
+
return fut.result()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class BackgroundLaunchedResult:
|
|
73
|
+
"""异步路径立即返回的凭证(coordinate 模式)。"""
|
|
74
|
+
status: str # always "async_launched"
|
|
75
|
+
agent_id: str
|
|
76
|
+
task_id: str
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class _EventCollectionResult:
|
|
81
|
+
"""子图事件收集结果。"""
|
|
82
|
+
collected_assistant_messages: list[Any]
|
|
83
|
+
terminal_reason: str
|
|
84
|
+
total_steps: int
|
|
85
|
+
error_message: str
|
|
86
|
+
last_tool_name: str
|
|
87
|
+
child_run_id: str | None
|
|
88
|
+
tool_step: int
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _to_message_dict(msg: Any) -> dict[str, Any]:
|
|
92
|
+
"""将 LangChain message 对象转为 dict,已是 dict 则直接返回。"""
|
|
93
|
+
if isinstance(msg, dict):
|
|
94
|
+
return msg
|
|
95
|
+
role = getattr(msg, "type", None) or getattr(msg, "role", "unknown")
|
|
96
|
+
# LangChain message type → role 映射
|
|
97
|
+
role_map = {"human": "user", "ai": "assistant", "system": "system", "tool": "tool"}
|
|
98
|
+
role = role_map.get(role, role)
|
|
99
|
+
content = getattr(msg, "content", "")
|
|
100
|
+
return {"role": role, "content": content}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _extract_final_content(messages: list[Any], allowed_tool_names: set[str] | None = None) -> str:
|
|
104
|
+
"""从累积的 assistant 消息中提取最终文本内容。
|
|
105
|
+
|
|
106
|
+
策略(对齐 CC finalizeAgentTool):
|
|
107
|
+
1. 从后往前找第一条有文本且不是纯工具调度消息的 AIMessage。
|
|
108
|
+
2. 若所有 AIMessage 都是纯工具调度(有 tool_calls 无文本),返回空串,
|
|
109
|
+
由调用方附诊断信息。
|
|
110
|
+
|
|
111
|
+
"纯工具调度消息"定义:tool_calls 非空,或内容含退化格式工具调用文本。
|
|
112
|
+
on_chat_model_end 在 ToolCallDegradationCorrector 处理前触发,因此
|
|
113
|
+
collected_assistant_messages 里的退化消息尚未被纠偏,需在此处识别并跳过。
|
|
114
|
+
"""
|
|
115
|
+
def _text_from_msg(msg: Any) -> str:
|
|
116
|
+
if isinstance(msg, dict):
|
|
117
|
+
if msg.get("role") in ("assistant", "ai"):
|
|
118
|
+
return str(msg.get("content", ""))
|
|
119
|
+
else:
|
|
120
|
+
if getattr(msg, "type", None) == "ai":
|
|
121
|
+
content = getattr(msg, "content", "")
|
|
122
|
+
if isinstance(content, str):
|
|
123
|
+
return content
|
|
124
|
+
if isinstance(content, list):
|
|
125
|
+
parts = [
|
|
126
|
+
item.get("text", "")
|
|
127
|
+
for item in content
|
|
128
|
+
if isinstance(item, dict) and item.get("type") == "text"
|
|
129
|
+
]
|
|
130
|
+
return "".join(parts)
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
def _has_tool_calls(msg: Any) -> bool:
|
|
134
|
+
# on_chat_model_end fires before ToolCallDegradationCorrector processes the message,
|
|
135
|
+
# so raw degraded messages have no tool_calls but contain degradation text patterns.
|
|
136
|
+
# Treat those as tool dispatch messages too, so they are skipped when extracting
|
|
137
|
+
# the final summary content.
|
|
138
|
+
if isinstance(msg, dict):
|
|
139
|
+
if bool(msg.get("tool_calls")):
|
|
140
|
+
return True
|
|
141
|
+
content = str(msg.get("content", ""))
|
|
142
|
+
else:
|
|
143
|
+
if bool(getattr(msg, "tool_calls", None)):
|
|
144
|
+
return True
|
|
145
|
+
content = getattr(msg, "content", "")
|
|
146
|
+
if not isinstance(content, str):
|
|
147
|
+
return False
|
|
148
|
+
# JSON-tag / prefix degradation formats
|
|
149
|
+
if "<tool_call>" in content or "CALL_TOOL:" in content or "TOOL_CALL:" in content:
|
|
150
|
+
return True
|
|
151
|
+
# XML child-element / self-closing formats: <ToolName>...</ToolName> or <ToolName ... />
|
|
152
|
+
# Only match tags whose names are in the known tool whitelist to avoid false positives
|
|
153
|
+
# on <env>, <tool_code>, or arbitrary HTML in model output.
|
|
154
|
+
if allowed_tool_names:
|
|
155
|
+
for name in allowed_tool_names:
|
|
156
|
+
if f"<{name}>" in content or f"<{name} " in content or f"<{name}/>" in content:
|
|
157
|
+
return True
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
for msg in reversed(messages):
|
|
161
|
+
text = _text_from_msg(msg)
|
|
162
|
+
if text and not _has_tool_calls(msg):
|
|
163
|
+
# 有文本且不是工具调度消息 — 这是真正的最终回复
|
|
164
|
+
return text
|
|
165
|
+
|
|
166
|
+
# 所有消息都是纯工具调度(或无文本),返回空串
|
|
167
|
+
return ""
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _resolve_subagent_model(ctx: SubagentContext, model: Any | None) -> Any:
|
|
171
|
+
if ctx.is_async:
|
|
172
|
+
raise RuntimeError(
|
|
173
|
+
"异步路径请调用 run_async_agent_lifecycle(),不要通过 run_subagent() 执行"
|
|
174
|
+
)
|
|
175
|
+
resolved_model = model if model is not None else ctx.model
|
|
176
|
+
if resolved_model is None:
|
|
177
|
+
raise ValueError("model is required for run_subagent")
|
|
178
|
+
return resolved_model
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _build_graph_input_messages(initial_messages: list[Any]) -> list[Any]:
|
|
182
|
+
return [
|
|
183
|
+
HumanMessage(content=m["content"])
|
|
184
|
+
if isinstance(m, dict) and m.get("role") == "user"
|
|
185
|
+
else m
|
|
186
|
+
for m in initial_messages
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _emit_progress(
|
|
191
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None,
|
|
192
|
+
event: SubagentProgressEvent,
|
|
193
|
+
) -> None:
|
|
194
|
+
if on_progress:
|
|
195
|
+
on_progress(event)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _handle_tool_start_event(
|
|
199
|
+
*,
|
|
200
|
+
ctx: SubagentContext,
|
|
201
|
+
event: dict[str, Any],
|
|
202
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None,
|
|
203
|
+
current_step: int,
|
|
204
|
+
) -> tuple[int, str]:
|
|
205
|
+
next_step = current_step + 1
|
|
206
|
+
tool_name = event.get("name", "")
|
|
207
|
+
_emit_progress(
|
|
208
|
+
on_progress,
|
|
209
|
+
SubagentProgressEvent(
|
|
210
|
+
type="tool_use",
|
|
211
|
+
subagent_type=ctx.subagent_type,
|
|
212
|
+
agent_id=ctx.agent_id,
|
|
213
|
+
step=next_step,
|
|
214
|
+
tool_name=tool_name,
|
|
215
|
+
summary=f"calling {tool_name}",
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
return next_step, tool_name
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _handle_tool_end_event(
|
|
222
|
+
*,
|
|
223
|
+
ctx: SubagentContext,
|
|
224
|
+
event: dict[str, Any],
|
|
225
|
+
data: dict[str, Any],
|
|
226
|
+
transcript: SubagentTranscriptManager,
|
|
227
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None,
|
|
228
|
+
step: int,
|
|
229
|
+
) -> None:
|
|
230
|
+
tool_name = event.get("name", "")
|
|
231
|
+
output = data.get("output")
|
|
232
|
+
output_str = str(output)[:500] if output is not None else ""
|
|
233
|
+
msg_dict = {"role": "tool", "tool_name": tool_name, "content": output_str}
|
|
234
|
+
transcript.append(msg_dict)
|
|
235
|
+
_emit_progress(
|
|
236
|
+
on_progress,
|
|
237
|
+
SubagentProgressEvent(
|
|
238
|
+
type="tool_result",
|
|
239
|
+
subagent_type=ctx.subagent_type,
|
|
240
|
+
agent_id=ctx.agent_id,
|
|
241
|
+
step=step,
|
|
242
|
+
tool_name=tool_name,
|
|
243
|
+
summary=output_str[:200],
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _handle_chat_model_end_event(
|
|
249
|
+
*,
|
|
250
|
+
ctx: SubagentContext,
|
|
251
|
+
data: dict[str, Any],
|
|
252
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None,
|
|
253
|
+
collected_assistant_messages: list[Any],
|
|
254
|
+
total_steps: int,
|
|
255
|
+
) -> int:
|
|
256
|
+
output = data.get("output")
|
|
257
|
+
if output is not None:
|
|
258
|
+
collected_assistant_messages.append(output)
|
|
259
|
+
has_tool_calls = bool(output is not None and getattr(output, "tool_calls", None))
|
|
260
|
+
if not has_tool_calls:
|
|
261
|
+
total_steps += 1
|
|
262
|
+
_emit_progress(
|
|
263
|
+
on_progress,
|
|
264
|
+
SubagentProgressEvent(
|
|
265
|
+
type="model_step",
|
|
266
|
+
subagent_type=ctx.subagent_type,
|
|
267
|
+
agent_id=ctx.agent_id,
|
|
268
|
+
step=total_steps,
|
|
269
|
+
summary="model responded",
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
return total_steps
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _handle_langgraph_end_event(
|
|
276
|
+
*,
|
|
277
|
+
data: dict[str, Any],
|
|
278
|
+
terminal_reason: str,
|
|
279
|
+
child_run_id: str | None,
|
|
280
|
+
) -> tuple[str, str | None]:
|
|
281
|
+
output = data.get("output") or {}
|
|
282
|
+
if not isinstance(output, dict):
|
|
283
|
+
return terminal_reason, child_run_id
|
|
284
|
+
tr = output.get("terminal_reason")
|
|
285
|
+
if tr:
|
|
286
|
+
terminal_reason = str(tr)
|
|
287
|
+
if child_run_id is None:
|
|
288
|
+
child_run_id = output.get("_run_id")
|
|
289
|
+
return terminal_reason, child_run_id
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
async def _collect_subagent_events(
|
|
293
|
+
*,
|
|
294
|
+
ctx: SubagentContext,
|
|
295
|
+
graph: Any,
|
|
296
|
+
graph_input: dict[str, Any],
|
|
297
|
+
run_tags: list[str],
|
|
298
|
+
transcript: SubagentTranscriptManager,
|
|
299
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None,
|
|
300
|
+
) -> _EventCollectionResult:
|
|
301
|
+
collected_assistant_messages: list[Any] = []
|
|
302
|
+
terminal_reason = "completed"
|
|
303
|
+
total_steps = 0
|
|
304
|
+
error_message = ""
|
|
305
|
+
last_tool_name = ""
|
|
306
|
+
child_run_id: str | None = None
|
|
307
|
+
tool_step = 0
|
|
308
|
+
try:
|
|
309
|
+
async for event in graph.astream_events(
|
|
310
|
+
graph_input,
|
|
311
|
+
config={"tags": run_tags, "recursion_limit": 9999},
|
|
312
|
+
version="v2",
|
|
313
|
+
):
|
|
314
|
+
event_name = event.get("event", "")
|
|
315
|
+
data = event.get("data") or {}
|
|
316
|
+
if event_name == "on_tool_start":
|
|
317
|
+
tool_step, last_tool_name = _handle_tool_start_event(
|
|
318
|
+
ctx=ctx,
|
|
319
|
+
event=event,
|
|
320
|
+
on_progress=on_progress,
|
|
321
|
+
current_step=tool_step,
|
|
322
|
+
)
|
|
323
|
+
elif event_name == "on_tool_end":
|
|
324
|
+
_handle_tool_end_event(
|
|
325
|
+
ctx=ctx,
|
|
326
|
+
event=event,
|
|
327
|
+
data=data,
|
|
328
|
+
transcript=transcript,
|
|
329
|
+
on_progress=on_progress,
|
|
330
|
+
step=tool_step,
|
|
331
|
+
)
|
|
332
|
+
elif event_name == "on_chat_model_end":
|
|
333
|
+
total_steps = _handle_chat_model_end_event(
|
|
334
|
+
ctx=ctx,
|
|
335
|
+
data=data,
|
|
336
|
+
on_progress=on_progress,
|
|
337
|
+
collected_assistant_messages=collected_assistant_messages,
|
|
338
|
+
total_steps=total_steps,
|
|
339
|
+
)
|
|
340
|
+
elif event_name == "on_chain_end" and event.get("name") == "LangGraph":
|
|
341
|
+
terminal_reason, child_run_id = _handle_langgraph_end_event(
|
|
342
|
+
data=data,
|
|
343
|
+
terminal_reason=terminal_reason,
|
|
344
|
+
child_run_id=child_run_id,
|
|
345
|
+
)
|
|
346
|
+
except Exception as exc:
|
|
347
|
+
logger.warning(
|
|
348
|
+
"subagent astream_events error agent_id=%s: %s",
|
|
349
|
+
ctx.agent_id,
|
|
350
|
+
exc,
|
|
351
|
+
exc_info=True,
|
|
352
|
+
)
|
|
353
|
+
error_message = str(exc)
|
|
354
|
+
terminal_reason = "error"
|
|
355
|
+
return _EventCollectionResult(
|
|
356
|
+
collected_assistant_messages=collected_assistant_messages,
|
|
357
|
+
terminal_reason=terminal_reason,
|
|
358
|
+
total_steps=total_steps,
|
|
359
|
+
error_message=error_message,
|
|
360
|
+
last_tool_name=last_tool_name,
|
|
361
|
+
child_run_id=child_run_id,
|
|
362
|
+
tool_step=tool_step,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _build_subagent_result(
|
|
367
|
+
*,
|
|
368
|
+
ctx: SubagentContext,
|
|
369
|
+
collected: _EventCollectionResult,
|
|
370
|
+
transcript: SubagentTranscriptManager,
|
|
371
|
+
allowed_tool_names: set[str] | None,
|
|
372
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None,
|
|
373
|
+
) -> SubagentResult:
|
|
374
|
+
content = _extract_final_content(collected.collected_assistant_messages, allowed_tool_names)
|
|
375
|
+
status = "error" if collected.terminal_reason == "error" else "completed"
|
|
376
|
+
transcript.flush([_to_message_dict(m) for m in collected.collected_assistant_messages])
|
|
377
|
+
if not content and status == "completed":
|
|
378
|
+
diag = f"total_steps={collected.total_steps}"
|
|
379
|
+
if collected.last_tool_name:
|
|
380
|
+
diag += f" last_tool={collected.last_tool_name}"
|
|
381
|
+
diag += f" terminal_reason={collected.terminal_reason}"
|
|
382
|
+
logger.warning(
|
|
383
|
+
"subagent completed with no content agent_id=%s %s",
|
|
384
|
+
ctx.agent_id, diag,
|
|
385
|
+
)
|
|
386
|
+
_emit_progress(
|
|
387
|
+
on_progress,
|
|
388
|
+
SubagentProgressEvent(
|
|
389
|
+
type="completed",
|
|
390
|
+
subagent_type=ctx.subagent_type,
|
|
391
|
+
agent_id=ctx.agent_id,
|
|
392
|
+
step=collected.total_steps,
|
|
393
|
+
summary=content[:200] if content else "done",
|
|
394
|
+
),
|
|
395
|
+
)
|
|
396
|
+
return SubagentResult(
|
|
397
|
+
agent_id=ctx.agent_id,
|
|
398
|
+
content=content,
|
|
399
|
+
status=status,
|
|
400
|
+
terminal_reason=collected.terminal_reason,
|
|
401
|
+
total_steps=collected.total_steps,
|
|
402
|
+
error_message=collected.error_message,
|
|
403
|
+
child_run_id=collected.child_run_id,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
async def run_subagent(
|
|
408
|
+
ctx: SubagentContext,
|
|
409
|
+
loader: Any,
|
|
410
|
+
model: Any | None = None,
|
|
411
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None = None,
|
|
412
|
+
allowed_tool_names: set[str] | None = None,
|
|
413
|
+
) -> SubagentResult:
|
|
414
|
+
"""运行子 Agent(plain/fork 同步路径)。
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
ctx: 子 Agent 执行上下文
|
|
418
|
+
loader: 子会话 ToolRuntimeLoader
|
|
419
|
+
model: 语言模型(str 或 BaseChatModel),None 时继承 ctx.model
|
|
420
|
+
on_progress: 进度回调(Channel B),每个工具调用步骤触发
|
|
421
|
+
allowed_tool_names: 工具白名单,用于识别 XML 退化格式(如 <Read>...</Read>)
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
SubagentResult
|
|
425
|
+
|
|
426
|
+
Raises:
|
|
427
|
+
RuntimeError: ctx.is_async=True 时(异步路径请调用 run_async_agent_lifecycle)
|
|
428
|
+
ValueError: model 与 ctx.model 均为 None 时
|
|
429
|
+
|
|
430
|
+
不变量:无论成功/失败,try/finally 确保 transcript.cleanup() 和
|
|
431
|
+
cleanup_subagent_context() 被调用。
|
|
432
|
+
"""
|
|
433
|
+
resolved_model = _resolve_subagent_model(ctx, model)
|
|
434
|
+
|
|
435
|
+
store = InMemoryTranscriptStore()
|
|
436
|
+
transcript = SubagentTranscriptManager(
|
|
437
|
+
store=store,
|
|
438
|
+
agent_id=ctx.agent_id,
|
|
439
|
+
parent_agent_id=ctx.parent_agent_id,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
graph = create_subagent_graph(ctx=ctx, loader=loader, model=resolved_model)
|
|
444
|
+
input_messages = _build_graph_input_messages(ctx.initial_messages)
|
|
445
|
+
graph_input = {"messages": input_messages}
|
|
446
|
+
run_tags = [f"subagent:{ctx.subagent_type}", f"agent_id:{ctx.agent_id}"]
|
|
447
|
+
collected = await _collect_subagent_events(
|
|
448
|
+
ctx=ctx,
|
|
449
|
+
graph=graph,
|
|
450
|
+
graph_input=graph_input,
|
|
451
|
+
run_tags=run_tags,
|
|
452
|
+
transcript=transcript,
|
|
453
|
+
on_progress=on_progress,
|
|
454
|
+
)
|
|
455
|
+
return _build_subagent_result(
|
|
456
|
+
ctx=ctx,
|
|
457
|
+
collected=collected,
|
|
458
|
+
transcript=transcript,
|
|
459
|
+
allowed_tool_names=allowed_tool_names,
|
|
460
|
+
on_progress=on_progress,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
finally:
|
|
464
|
+
transcript.cleanup()
|
|
465
|
+
cleanup_subagent_context(ctx)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
async def run_subagent_with_background_signal(
|
|
469
|
+
ctx: SubagentContext,
|
|
470
|
+
loader: Any,
|
|
471
|
+
model: Any | None = None,
|
|
472
|
+
on_progress: Callable[[SubagentProgressEvent], None] | None = None,
|
|
473
|
+
background_signal: asyncio.Event | None = None,
|
|
474
|
+
on_background_request: Callable[[SubagentContext], str] | None = None,
|
|
475
|
+
allowed_tool_names: set[str] | None = None,
|
|
476
|
+
) -> SubagentResult | BackgroundLaunchedResult:
|
|
477
|
+
"""同步执行中监听后台化信号(CC Promise.race 语义)。"""
|
|
478
|
+
agent_task = asyncio.create_task(
|
|
479
|
+
run_subagent(ctx=ctx, loader=loader, model=model, on_progress=on_progress, allowed_tool_names=allowed_tool_names)
|
|
480
|
+
)
|
|
481
|
+
if background_signal is None:
|
|
482
|
+
return await agent_task
|
|
483
|
+
|
|
484
|
+
signal_task = asyncio.create_task(background_signal.wait())
|
|
485
|
+
done, pending = await asyncio.wait(
|
|
486
|
+
{agent_task, signal_task},
|
|
487
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
if signal_task in done and signal_task.result():
|
|
491
|
+
agent_task.cancel()
|
|
492
|
+
for task in pending:
|
|
493
|
+
task.cancel()
|
|
494
|
+
if on_background_request is None:
|
|
495
|
+
raise RuntimeError("background signal received but no handler configured")
|
|
496
|
+
task_id = on_background_request(ctx)
|
|
497
|
+
return BackgroundLaunchedResult(
|
|
498
|
+
status="async_launched",
|
|
499
|
+
agent_id=ctx.agent_id,
|
|
500
|
+
task_id=task_id,
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
signal_task.cancel()
|
|
504
|
+
return await agent_task
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Subagent Transcript 存储与管理
|
|
2
|
+
|
|
3
|
+
职责:管理单个子会话 transcript 生命周期
|
|
4
|
+
在整体链路中的位置:runner.py 构造 SubagentTranscriptManager → append/flush/cleanup
|
|
5
|
+
CC 对照:src/agent/subagent/transcript.ts TranscriptStore 接口
|
|
6
|
+
当前裁剪范围:v1 Protocol + 内存实现 + JSONL 文件后端
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Protocol, runtime_checkable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class SubagentTranscriptStore(Protocol):
|
|
17
|
+
"""Transcript 存储后端契约(可换实现:replay/内存/日志)"""
|
|
18
|
+
|
|
19
|
+
def write_transcript(
|
|
20
|
+
self,
|
|
21
|
+
agent_id: str,
|
|
22
|
+
messages: list[dict[str, Any]],
|
|
23
|
+
metadata: dict[str, Any] | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""写入完整 transcript"""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def append_message(self, agent_id: str, message: dict[str, Any]) -> None:
|
|
29
|
+
"""追加单条消息"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
def read_transcript(self, agent_id: str) -> list[dict[str, Any]]:
|
|
33
|
+
"""读取 transcript"""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
def cleanup_transcript(self, agent_id: str) -> None:
|
|
37
|
+
"""清理 transcript"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class SubagentTranscriptManager:
|
|
43
|
+
"""管理单个子会话 transcript 生命周期
|
|
44
|
+
|
|
45
|
+
协作者:依赖 SubagentTranscriptStore(注入)
|
|
46
|
+
不变量:flush() 必须在 cleanup() 之前调用
|
|
47
|
+
"""
|
|
48
|
+
store: SubagentTranscriptStore
|
|
49
|
+
agent_id: str
|
|
50
|
+
parent_agent_id: str | None = None
|
|
51
|
+
_buffer: list[dict[str, Any]] = field(default_factory=list)
|
|
52
|
+
|
|
53
|
+
def append(self, message: dict[str, Any]) -> None:
|
|
54
|
+
"""追加消息到缓冲区并写入 store"""
|
|
55
|
+
self._buffer.append(message)
|
|
56
|
+
self.store.append_message(self.agent_id, message)
|
|
57
|
+
|
|
58
|
+
def flush(self, final_messages: list[dict[str, Any]]) -> None:
|
|
59
|
+
"""写入完整 transcript(含元数据)"""
|
|
60
|
+
metadata = {"parent_agent_id": self.parent_agent_id} if self.parent_agent_id else {}
|
|
61
|
+
self.store.write_transcript(self.agent_id, final_messages, metadata)
|
|
62
|
+
|
|
63
|
+
def cleanup(self) -> None:
|
|
64
|
+
"""清理缓冲区与 store"""
|
|
65
|
+
self._buffer.clear()
|
|
66
|
+
self.store.cleanup_transcript(self.agent_id)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class InMemoryTranscriptStore:
|
|
70
|
+
"""内存实现(供单测与 stub 使用)"""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
self._storage: dict[str, list[dict[str, Any]]] = {}
|
|
74
|
+
|
|
75
|
+
def write_transcript(
|
|
76
|
+
self,
|
|
77
|
+
agent_id: str,
|
|
78
|
+
messages: list[dict[str, Any]],
|
|
79
|
+
metadata: dict[str, Any] | None = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
self._storage[agent_id] = messages.copy()
|
|
82
|
+
|
|
83
|
+
def append_message(self, agent_id: str, message: dict[str, Any]) -> None:
|
|
84
|
+
if agent_id not in self._storage:
|
|
85
|
+
self._storage[agent_id] = []
|
|
86
|
+
self._storage[agent_id].append(message)
|
|
87
|
+
|
|
88
|
+
def read_transcript(self, agent_id: str) -> list[dict[str, Any]]:
|
|
89
|
+
return self._storage.get(agent_id, [])
|
|
90
|
+
|
|
91
|
+
def cleanup_transcript(self, agent_id: str) -> None:
|
|
92
|
+
self._storage.pop(agent_id, None)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class JsonlTranscriptStore:
|
|
96
|
+
"""JSONL 文件实现(按 session/agent 隔离 transcript)。"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, transcripts_dir: str | Path, session_id: str) -> None:
|
|
99
|
+
self._transcripts_dir = Path(transcripts_dir)
|
|
100
|
+
self._session_id = session_id
|
|
101
|
+
|
|
102
|
+
def write_transcript(
|
|
103
|
+
self,
|
|
104
|
+
agent_id: str,
|
|
105
|
+
messages: list[dict[str, Any]],
|
|
106
|
+
metadata: dict[str, Any] | None = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
path = self._resolve_path(agent_id)
|
|
109
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
with path.open("w", encoding="utf-8") as handle:
|
|
111
|
+
for message in messages:
|
|
112
|
+
handle.write(self._encode_line(message))
|
|
113
|
+
if metadata:
|
|
114
|
+
handle.write(self._encode_line({"type": "metadata", **metadata}))
|
|
115
|
+
|
|
116
|
+
def append_message(self, agent_id: str, message: dict[str, Any]) -> None:
|
|
117
|
+
path = self._resolve_path(agent_id)
|
|
118
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
with path.open("a", encoding="utf-8") as handle:
|
|
120
|
+
handle.write(self._encode_line(message))
|
|
121
|
+
|
|
122
|
+
def read_transcript(self, agent_id: str) -> list[dict[str, Any]]:
|
|
123
|
+
path = self._resolve_path(agent_id)
|
|
124
|
+
if not path.exists():
|
|
125
|
+
return []
|
|
126
|
+
messages: list[dict[str, Any]] = []
|
|
127
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
128
|
+
for raw_line in handle:
|
|
129
|
+
decoded = self._decode_line(raw_line)
|
|
130
|
+
if decoded is None:
|
|
131
|
+
continue
|
|
132
|
+
if decoded.get("type") == "metadata":
|
|
133
|
+
continue
|
|
134
|
+
messages.append(decoded)
|
|
135
|
+
return messages
|
|
136
|
+
|
|
137
|
+
def read_transcript_after_last_boundary(self, agent_id: str) -> list[dict[str, Any]]:
|
|
138
|
+
"""读取最后一个 compact_boundary 之后的消息(边界本身不返回)。"""
|
|
139
|
+
messages = self.read_transcript(agent_id)
|
|
140
|
+
last_boundary_index = -1
|
|
141
|
+
for idx, item in enumerate(messages):
|
|
142
|
+
if item.get("type") == "compact_boundary":
|
|
143
|
+
last_boundary_index = idx
|
|
144
|
+
if last_boundary_index < 0:
|
|
145
|
+
return messages
|
|
146
|
+
return messages[last_boundary_index + 1 :]
|
|
147
|
+
|
|
148
|
+
def cleanup_transcript(self, agent_id: str) -> None:
|
|
149
|
+
path = self._resolve_path(agent_id)
|
|
150
|
+
if path.exists():
|
|
151
|
+
path.unlink()
|
|
152
|
+
session_dir = path.parent
|
|
153
|
+
if session_dir.exists() and not any(session_dir.iterdir()):
|
|
154
|
+
session_dir.rmdir()
|
|
155
|
+
|
|
156
|
+
def _resolve_path(self, agent_id: str) -> Path:
|
|
157
|
+
return self._transcripts_dir / self._session_id / f"{agent_id}.jsonl"
|
|
158
|
+
|
|
159
|
+
def _encode_line(self, payload: dict[str, Any]) -> str:
|
|
160
|
+
return json.dumps(payload, ensure_ascii=False) + "\n"
|
|
161
|
+
|
|
162
|
+
def _decode_line(self, line: str) -> dict[str, Any] | None:
|
|
163
|
+
stripped = line.strip()
|
|
164
|
+
if stripped == "":
|
|
165
|
+
return None
|
|
166
|
+
try:
|
|
167
|
+
decoded = json.loads(stripped)
|
|
168
|
+
except json.JSONDecodeError:
|
|
169
|
+
return None
|
|
170
|
+
if not isinstance(decoded, dict):
|
|
171
|
+
return None
|
|
172
|
+
return decoded
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""instruction memory — Layer 1 规则加载与提示词注入。"""
|
|
2
|
+
|
|
3
|
+
from .types import ConditionalRule, InstructionMemoryConfig, LoadedRuleSet, RuleFile, RuleLayer
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"RuleLayer",
|
|
7
|
+
"RuleFile",
|
|
8
|
+
"ConditionalRule",
|
|
9
|
+
"LoadedRuleSet",
|
|
10
|
+
"InstructionMemoryConfig",
|
|
11
|
+
]
|
|
12
|
+
|