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,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/integrations/provider_factory.py — queued provider 统一配置入口
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
为 task 侧提供一个统一的 provider 装配工厂,减少调用方手工拼装成本。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from langchain_agentx.workspace import resolve_agent_workspace_config
|
|
14
|
+
|
|
15
|
+
from .prefetch_providers import MemoryPrefetchProvider
|
|
16
|
+
from ..skill_prefetch import SkillPrefetchProvider
|
|
17
|
+
from .queued_command_provider import InMemoryQueuedCommandProvider
|
|
18
|
+
from .sqlite_queued_command_provider import SqliteQueuedCommandProvider
|
|
19
|
+
from .tool_use_summary_provider import ToolUseSummaryProvider
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
BackendType = Literal["in_memory", "sqlite"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class ProviderFactoryConfig:
|
|
27
|
+
backend_type: BackendType = "in_memory"
|
|
28
|
+
sqlite_db_path: str | None = None
|
|
29
|
+
workspace_root: str | None = None
|
|
30
|
+
agent_home: str = ".langchain_agentx"
|
|
31
|
+
max_queue_size: int | None = None
|
|
32
|
+
ttl_sec: float | None = None
|
|
33
|
+
enable_memory_prefetch: bool = True
|
|
34
|
+
enable_skill_prefetch: bool = True
|
|
35
|
+
enable_tool_summary: bool = True
|
|
36
|
+
summary_error_ratio_now_threshold: float = 0.5
|
|
37
|
+
summary_avg_latency_now_threshold_ms: int = 2000
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class ProviderBundle:
|
|
42
|
+
memory_prefetch: MemoryPrefetchProvider | None
|
|
43
|
+
skill_prefetch: SkillPrefetchProvider | None
|
|
44
|
+
tool_summary: ToolUseSummaryProvider | None
|
|
45
|
+
|
|
46
|
+
def as_list(self) -> list:
|
|
47
|
+
return [item for item in [self.memory_prefetch, self.skill_prefetch, self.tool_summary] if item is not None]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_provider_bundle(config: ProviderFactoryConfig) -> ProviderBundle:
|
|
51
|
+
workspace_cfg = resolve_agent_workspace_config(
|
|
52
|
+
workspace_root=config.workspace_root or Path.cwd(),
|
|
53
|
+
agent_home=config.agent_home,
|
|
54
|
+
)
|
|
55
|
+
backend = _build_backend(config)
|
|
56
|
+
memory_prefetch = (
|
|
57
|
+
MemoryPrefetchProvider(backend=backend)
|
|
58
|
+
if config.enable_memory_prefetch
|
|
59
|
+
else None
|
|
60
|
+
)
|
|
61
|
+
skill_prefetch = (
|
|
62
|
+
SkillPrefetchProvider(
|
|
63
|
+
workspace_root=workspace_cfg.workspace_root,
|
|
64
|
+
agent_home=workspace_cfg.agent_home,
|
|
65
|
+
)
|
|
66
|
+
if config.enable_skill_prefetch
|
|
67
|
+
else None
|
|
68
|
+
)
|
|
69
|
+
tool_summary = (
|
|
70
|
+
ToolUseSummaryProvider(
|
|
71
|
+
backend=backend,
|
|
72
|
+
error_ratio_now_threshold=config.summary_error_ratio_now_threshold,
|
|
73
|
+
avg_latency_now_threshold_ms=config.summary_avg_latency_now_threshold_ms,
|
|
74
|
+
)
|
|
75
|
+
if config.enable_tool_summary
|
|
76
|
+
else None
|
|
77
|
+
)
|
|
78
|
+
return ProviderBundle(
|
|
79
|
+
memory_prefetch=memory_prefetch,
|
|
80
|
+
skill_prefetch=skill_prefetch,
|
|
81
|
+
tool_summary=tool_summary,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _build_backend(config: ProviderFactoryConfig):
|
|
86
|
+
if config.backend_type == "sqlite":
|
|
87
|
+
workspace_cfg = resolve_agent_workspace_config(
|
|
88
|
+
workspace_root=config.workspace_root or Path.cwd(),
|
|
89
|
+
agent_home=config.agent_home,
|
|
90
|
+
)
|
|
91
|
+
db_path = config.sqlite_db_path or str(workspace_cfg.task_runtime_db_path)
|
|
92
|
+
Path(db_path).expanduser().resolve().parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
return SqliteQueuedCommandProvider(
|
|
94
|
+
db_path,
|
|
95
|
+
max_queue_size=config.max_queue_size,
|
|
96
|
+
ttl_sec=config.ttl_sec,
|
|
97
|
+
recover_reserved_on_startup=True,
|
|
98
|
+
)
|
|
99
|
+
return InMemoryQueuedCommandProvider(
|
|
100
|
+
max_queue_size=config.max_queue_size,
|
|
101
|
+
ttl_sec=config.ttl_sec,
|
|
102
|
+
)
|
|
103
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/integrations/queued_command_provider.py — 通用 queued-command provider
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
为 memory/skill prefetch 等“非 task_runtime 来源”提供统一的队列能力:
|
|
6
|
+
1. enqueue:写入待注入命令
|
|
7
|
+
2. build_batch:按 scope/priority reserve 批次
|
|
8
|
+
3. ack_batch / nack_batch:两阶段确认
|
|
9
|
+
4. has_pending:供 loop_controller 退出守卫探测
|
|
10
|
+
|
|
11
|
+
边界:
|
|
12
|
+
- 仅负责 queued-command 的队列与批次确认
|
|
13
|
+
- 不依赖 TaskRuntime,不参与任务生命周期状态机
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from threading import RLock
|
|
21
|
+
from uuid import uuid4
|
|
22
|
+
import time
|
|
23
|
+
|
|
24
|
+
from ..core.types import QueuePriority, TaskScope
|
|
25
|
+
from .loop_adapter import LoopInjectionBatch
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_PRIORITY_ORDER = {
|
|
29
|
+
QueuePriority.NOW: 0,
|
|
30
|
+
QueuePriority.NEXT: 1,
|
|
31
|
+
QueuePriority.LATER: 2,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class QueuedCommandEnvelope:
|
|
37
|
+
command_id: str
|
|
38
|
+
source: str
|
|
39
|
+
summary: str
|
|
40
|
+
scope: TaskScope
|
|
41
|
+
priority: QueuePriority = QueuePriority.NEXT
|
|
42
|
+
payload: dict = field(default_factory=dict)
|
|
43
|
+
dedup_key: str | None = None
|
|
44
|
+
created_at: float = field(default_factory=time.time)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class InMemoryQueuedCommandProvider:
|
|
48
|
+
"""最小可用 queued-command provider(内存版)。"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
*,
|
|
53
|
+
max_queue_size: int | None = None,
|
|
54
|
+
ttl_sec: float | None = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
self._lock = RLock()
|
|
57
|
+
self._queue: list[QueuedCommandEnvelope] = []
|
|
58
|
+
self._reserved_batches: dict[str, list[QueuedCommandEnvelope]] = {}
|
|
59
|
+
self._max_queue_size = max_queue_size
|
|
60
|
+
self._ttl_sec = ttl_sec
|
|
61
|
+
|
|
62
|
+
def enqueue(self, env: QueuedCommandEnvelope) -> None:
|
|
63
|
+
with self._lock:
|
|
64
|
+
self._evict_expired_locked(now_ts=time.time())
|
|
65
|
+
# 可选去重:同 scope + dedup_key 仅保留最新一条
|
|
66
|
+
if env.dedup_key:
|
|
67
|
+
self._queue = [
|
|
68
|
+
item
|
|
69
|
+
for item in self._queue
|
|
70
|
+
if not (
|
|
71
|
+
item.scope.agent_id == env.scope.agent_id
|
|
72
|
+
and item.dedup_key == env.dedup_key
|
|
73
|
+
)
|
|
74
|
+
]
|
|
75
|
+
self._queue.append(env)
|
|
76
|
+
self._evict_over_capacity_locked()
|
|
77
|
+
|
|
78
|
+
def build_batch(
|
|
79
|
+
self,
|
|
80
|
+
scope: TaskScope,
|
|
81
|
+
max_priority: QueuePriority = QueuePriority.NEXT,
|
|
82
|
+
*,
|
|
83
|
+
limit: int = 8,
|
|
84
|
+
) -> LoopInjectionBatch:
|
|
85
|
+
if limit <= 0:
|
|
86
|
+
return LoopInjectionBatch(batch_id="", command_ids=[], attachment_messages=[])
|
|
87
|
+
threshold = _PRIORITY_ORDER[max_priority]
|
|
88
|
+
with self._lock:
|
|
89
|
+
self._evict_expired_locked(now_ts=time.time())
|
|
90
|
+
candidates = [
|
|
91
|
+
item
|
|
92
|
+
for item in self._queue
|
|
93
|
+
if item.scope.agent_id == scope.agent_id
|
|
94
|
+
and _PRIORITY_ORDER[item.priority] <= threshold
|
|
95
|
+
]
|
|
96
|
+
candidates.sort(key=lambda item: _PRIORITY_ORDER[item.priority])
|
|
97
|
+
selected = candidates[:limit]
|
|
98
|
+
if not selected:
|
|
99
|
+
return LoopInjectionBatch(batch_id="", command_ids=[], attachment_messages=[])
|
|
100
|
+
|
|
101
|
+
selected_ids = {item.command_id for item in selected}
|
|
102
|
+
self._queue = [item for item in self._queue if item.command_id not in selected_ids]
|
|
103
|
+
batch_id = f"qbatch_{uuid4().hex[:10]}"
|
|
104
|
+
self._reserved_batches[batch_id] = selected
|
|
105
|
+
return LoopInjectionBatch(
|
|
106
|
+
batch_id=batch_id,
|
|
107
|
+
command_ids=[item.command_id for item in selected],
|
|
108
|
+
attachment_messages=[_to_attachment_message(item) for item in selected],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def ack_batch(self, batch: LoopInjectionBatch) -> None:
|
|
112
|
+
if not batch.batch_id:
|
|
113
|
+
return
|
|
114
|
+
with self._lock:
|
|
115
|
+
self._reserved_batches.pop(batch.batch_id, None)
|
|
116
|
+
|
|
117
|
+
def nack_batch(self, batch: LoopInjectionBatch, *, requeue: bool = True) -> None:
|
|
118
|
+
if not batch.batch_id:
|
|
119
|
+
return
|
|
120
|
+
with self._lock:
|
|
121
|
+
reserved = self._reserved_batches.pop(batch.batch_id, None)
|
|
122
|
+
if reserved and requeue:
|
|
123
|
+
self._queue.extend(reserved)
|
|
124
|
+
|
|
125
|
+
def has_pending(
|
|
126
|
+
self, scope: TaskScope, max_priority: QueuePriority = QueuePriority.NEXT
|
|
127
|
+
) -> bool:
|
|
128
|
+
threshold = _PRIORITY_ORDER[max_priority]
|
|
129
|
+
with self._lock:
|
|
130
|
+
self._evict_expired_locked(now_ts=time.time())
|
|
131
|
+
return any(
|
|
132
|
+
item.scope.agent_id == scope.agent_id
|
|
133
|
+
and _PRIORITY_ORDER[item.priority] <= threshold
|
|
134
|
+
for item in self._queue
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def ingest_from_messages(self, *, scope: TaskScope, messages: list[Any]) -> int:
|
|
138
|
+
"""默认无 loop messages 桥接;子类或包装 provider 可覆盖。"""
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
def evict_expired(self, *, now_ts: float | None = None) -> int:
|
|
142
|
+
with self._lock:
|
|
143
|
+
return self._evict_expired_locked(now_ts=time.time() if now_ts is None else now_ts)
|
|
144
|
+
|
|
145
|
+
def _evict_over_capacity_locked(self) -> None:
|
|
146
|
+
if self._max_queue_size is None:
|
|
147
|
+
return
|
|
148
|
+
if self._max_queue_size < 0:
|
|
149
|
+
return
|
|
150
|
+
overflow = len(self._queue) - self._max_queue_size
|
|
151
|
+
if overflow <= 0:
|
|
152
|
+
return
|
|
153
|
+
# 保留高优先级:按优先级+创建时间排序,淘汰最不重要且最老的记录
|
|
154
|
+
self._queue.sort(
|
|
155
|
+
key=lambda item: (_PRIORITY_ORDER[item.priority], item.created_at)
|
|
156
|
+
)
|
|
157
|
+
# 末尾是低优先级;从末尾逆向淘汰 overflow 个
|
|
158
|
+
self._queue = self._queue[: self._max_queue_size]
|
|
159
|
+
|
|
160
|
+
def _evict_expired_locked(self, *, now_ts: float) -> int:
|
|
161
|
+
if self._ttl_sec is None or self._ttl_sec <= 0:
|
|
162
|
+
return 0
|
|
163
|
+
before = len(self._queue)
|
|
164
|
+
self._queue = [
|
|
165
|
+
item for item in self._queue if (now_ts - float(item.created_at)) < self._ttl_sec
|
|
166
|
+
]
|
|
167
|
+
return before - len(self._queue)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _to_attachment_message(env: QueuedCommandEnvelope) -> dict:
|
|
171
|
+
text = f"[queued_command][{env.source}] {env.summary}"
|
|
172
|
+
return {
|
|
173
|
+
"role": "user",
|
|
174
|
+
"content": [{"type": "text", "text": text}],
|
|
175
|
+
"metadata": {
|
|
176
|
+
"queued_command": {
|
|
177
|
+
"command_id": env.command_id,
|
|
178
|
+
"source": env.source,
|
|
179
|
+
"priority": env.priority.value,
|
|
180
|
+
"payload": env.payload,
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
task_runtime/integrations/sqlite_queued_command_provider.py — 持久化 queued-command provider
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
提供 queued command 的 SQLite 持久化实现,支持:
|
|
6
|
+
1. enqueue/build_batch/ack/nack/has_pending
|
|
7
|
+
2. TTL 回收与容量回收
|
|
8
|
+
3. 进程重启后的 reserved 恢复(crash-recovery)
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sqlite3
|
|
14
|
+
import time
|
|
15
|
+
from threading import RLock
|
|
16
|
+
from typing import Any
|
|
17
|
+
from uuid import uuid4
|
|
18
|
+
|
|
19
|
+
from ..core.types import QueuePriority, TaskScope
|
|
20
|
+
from .loop_adapter import LoopInjectionBatch
|
|
21
|
+
from .queued_command_provider import QueuedCommandEnvelope
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_PRIORITY_ORDER = {
|
|
25
|
+
QueuePriority.NOW: 0,
|
|
26
|
+
QueuePriority.NEXT: 1,
|
|
27
|
+
QueuePriority.LATER: 2,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SqliteQueuedCommandProvider:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
db_path: str,
|
|
35
|
+
*,
|
|
36
|
+
max_queue_size: int | None = None,
|
|
37
|
+
ttl_sec: float | None = None,
|
|
38
|
+
recover_reserved_on_startup: bool = True,
|
|
39
|
+
) -> None:
|
|
40
|
+
self._lock = RLock()
|
|
41
|
+
self._conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
42
|
+
self._conn.row_factory = sqlite3.Row
|
|
43
|
+
self._max_queue_size = max_queue_size
|
|
44
|
+
self._ttl_sec = ttl_sec
|
|
45
|
+
self._init_schema()
|
|
46
|
+
if recover_reserved_on_startup:
|
|
47
|
+
self._recover_reserved()
|
|
48
|
+
|
|
49
|
+
def _init_schema(self) -> None:
|
|
50
|
+
with self._conn:
|
|
51
|
+
self._conn.execute(
|
|
52
|
+
"""
|
|
53
|
+
CREATE TABLE IF NOT EXISTS queued_commands (
|
|
54
|
+
command_id TEXT PRIMARY KEY,
|
|
55
|
+
source TEXT NOT NULL,
|
|
56
|
+
summary TEXT NOT NULL,
|
|
57
|
+
scope_agent_id TEXT,
|
|
58
|
+
priority TEXT NOT NULL,
|
|
59
|
+
payload_json TEXT NOT NULL,
|
|
60
|
+
dedup_key TEXT,
|
|
61
|
+
created_at REAL NOT NULL
|
|
62
|
+
)
|
|
63
|
+
"""
|
|
64
|
+
)
|
|
65
|
+
self._conn.execute(
|
|
66
|
+
"""
|
|
67
|
+
CREATE TABLE IF NOT EXISTS reserved_commands (
|
|
68
|
+
batch_id TEXT NOT NULL,
|
|
69
|
+
command_id TEXT NOT NULL,
|
|
70
|
+
source TEXT NOT NULL,
|
|
71
|
+
summary TEXT NOT NULL,
|
|
72
|
+
scope_agent_id TEXT,
|
|
73
|
+
priority TEXT NOT NULL,
|
|
74
|
+
payload_json TEXT NOT NULL,
|
|
75
|
+
dedup_key TEXT,
|
|
76
|
+
created_at REAL NOT NULL,
|
|
77
|
+
PRIMARY KEY (batch_id, command_id)
|
|
78
|
+
)
|
|
79
|
+
"""
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def enqueue(self, env: QueuedCommandEnvelope) -> None:
|
|
83
|
+
with self._lock, self._conn:
|
|
84
|
+
self._evict_expired_locked(now_ts=time.time())
|
|
85
|
+
if env.dedup_key:
|
|
86
|
+
self._conn.execute(
|
|
87
|
+
"DELETE FROM queued_commands WHERE scope_agent_id IS ? AND dedup_key = ?",
|
|
88
|
+
(env.scope.agent_id, env.dedup_key),
|
|
89
|
+
)
|
|
90
|
+
self._conn.execute(
|
|
91
|
+
"""
|
|
92
|
+
INSERT OR REPLACE INTO queued_commands(
|
|
93
|
+
command_id, source, summary, scope_agent_id, priority, payload_json, dedup_key, created_at
|
|
94
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
95
|
+
""",
|
|
96
|
+
(
|
|
97
|
+
env.command_id,
|
|
98
|
+
env.source,
|
|
99
|
+
env.summary,
|
|
100
|
+
env.scope.agent_id,
|
|
101
|
+
env.priority.value,
|
|
102
|
+
json.dumps(env.payload, ensure_ascii=True),
|
|
103
|
+
env.dedup_key,
|
|
104
|
+
float(env.created_at),
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
self._evict_over_capacity_locked()
|
|
108
|
+
|
|
109
|
+
def build_batch(
|
|
110
|
+
self,
|
|
111
|
+
scope: TaskScope,
|
|
112
|
+
max_priority: QueuePriority = QueuePriority.NEXT,
|
|
113
|
+
*,
|
|
114
|
+
limit: int = 8,
|
|
115
|
+
) -> LoopInjectionBatch:
|
|
116
|
+
if limit <= 0:
|
|
117
|
+
return LoopInjectionBatch(batch_id="", command_ids=[], attachment_messages=[])
|
|
118
|
+
threshold = _PRIORITY_ORDER[max_priority]
|
|
119
|
+
with self._lock, self._conn:
|
|
120
|
+
self._evict_expired_locked(now_ts=time.time())
|
|
121
|
+
rows = self._conn.execute(
|
|
122
|
+
"""
|
|
123
|
+
SELECT * FROM queued_commands
|
|
124
|
+
WHERE scope_agent_id IS ?
|
|
125
|
+
ORDER BY
|
|
126
|
+
CASE priority
|
|
127
|
+
WHEN 'now' THEN 0
|
|
128
|
+
WHEN 'next' THEN 1
|
|
129
|
+
ELSE 2
|
|
130
|
+
END ASC,
|
|
131
|
+
created_at ASC
|
|
132
|
+
""",
|
|
133
|
+
(scope.agent_id,),
|
|
134
|
+
).fetchall()
|
|
135
|
+
selected = [
|
|
136
|
+
row
|
|
137
|
+
for row in rows
|
|
138
|
+
if _PRIORITY_ORDER[QueuePriority(str(row["priority"]))] <= threshold
|
|
139
|
+
][:limit]
|
|
140
|
+
if not selected:
|
|
141
|
+
return LoopInjectionBatch(batch_id="", command_ids=[], attachment_messages=[])
|
|
142
|
+
|
|
143
|
+
batch_id = f"sqbatch_{uuid4().hex[:10]}"
|
|
144
|
+
command_ids = [str(item["command_id"]) for item in selected]
|
|
145
|
+
for row in selected:
|
|
146
|
+
self._conn.execute(
|
|
147
|
+
"""
|
|
148
|
+
INSERT OR REPLACE INTO reserved_commands(
|
|
149
|
+
batch_id, command_id, source, summary, scope_agent_id, priority, payload_json, dedup_key, created_at
|
|
150
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
151
|
+
""",
|
|
152
|
+
(
|
|
153
|
+
batch_id,
|
|
154
|
+
row["command_id"],
|
|
155
|
+
row["source"],
|
|
156
|
+
row["summary"],
|
|
157
|
+
row["scope_agent_id"],
|
|
158
|
+
row["priority"],
|
|
159
|
+
row["payload_json"],
|
|
160
|
+
row["dedup_key"],
|
|
161
|
+
row["created_at"],
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
placeholders = ",".join("?" for _ in command_ids)
|
|
165
|
+
self._conn.execute(
|
|
166
|
+
f"DELETE FROM queued_commands WHERE command_id IN ({placeholders})",
|
|
167
|
+
command_ids,
|
|
168
|
+
)
|
|
169
|
+
messages = [
|
|
170
|
+
_to_attachment_message(_row_to_env(row))
|
|
171
|
+
for row in selected
|
|
172
|
+
]
|
|
173
|
+
return LoopInjectionBatch(
|
|
174
|
+
batch_id=batch_id,
|
|
175
|
+
command_ids=command_ids,
|
|
176
|
+
attachment_messages=messages,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def ack_batch(self, batch: LoopInjectionBatch) -> None:
|
|
180
|
+
if not batch.batch_id:
|
|
181
|
+
return
|
|
182
|
+
with self._lock, self._conn:
|
|
183
|
+
self._conn.execute(
|
|
184
|
+
"DELETE FROM reserved_commands WHERE batch_id = ?",
|
|
185
|
+
(batch.batch_id,),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def nack_batch(self, batch: LoopInjectionBatch, *, requeue: bool = True) -> None:
|
|
189
|
+
if not batch.batch_id:
|
|
190
|
+
return
|
|
191
|
+
with self._lock, self._conn:
|
|
192
|
+
rows = self._conn.execute(
|
|
193
|
+
"SELECT * FROM reserved_commands WHERE batch_id = ?",
|
|
194
|
+
(batch.batch_id,),
|
|
195
|
+
).fetchall()
|
|
196
|
+
if requeue:
|
|
197
|
+
for row in rows:
|
|
198
|
+
self._conn.execute(
|
|
199
|
+
"""
|
|
200
|
+
INSERT OR REPLACE INTO queued_commands(
|
|
201
|
+
command_id, source, summary, scope_agent_id, priority, payload_json, dedup_key, created_at
|
|
202
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
203
|
+
""",
|
|
204
|
+
(
|
|
205
|
+
row["command_id"],
|
|
206
|
+
row["source"],
|
|
207
|
+
row["summary"],
|
|
208
|
+
row["scope_agent_id"],
|
|
209
|
+
row["priority"],
|
|
210
|
+
row["payload_json"],
|
|
211
|
+
row["dedup_key"],
|
|
212
|
+
row["created_at"],
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
self._conn.execute(
|
|
216
|
+
"DELETE FROM reserved_commands WHERE batch_id = ?",
|
|
217
|
+
(batch.batch_id,),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def has_pending(
|
|
221
|
+
self, scope: TaskScope, max_priority: QueuePriority = QueuePriority.NEXT
|
|
222
|
+
) -> bool:
|
|
223
|
+
threshold = _PRIORITY_ORDER[max_priority]
|
|
224
|
+
with self._lock:
|
|
225
|
+
self._evict_expired_locked(now_ts=time.time())
|
|
226
|
+
rows = self._conn.execute(
|
|
227
|
+
"SELECT priority FROM queued_commands WHERE scope_agent_id IS ?",
|
|
228
|
+
(scope.agent_id,),
|
|
229
|
+
).fetchall()
|
|
230
|
+
return any(
|
|
231
|
+
_PRIORITY_ORDER[QueuePriority(str(item["priority"]))] <= threshold
|
|
232
|
+
for item in rows
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def ingest_from_messages(self, *, scope: TaskScope, messages: list[Any]) -> int:
|
|
236
|
+
return 0
|
|
237
|
+
|
|
238
|
+
def evict_expired(self, *, now_ts: float | None = None) -> int:
|
|
239
|
+
with self._lock:
|
|
240
|
+
return self._evict_expired_locked(now_ts=time.time() if now_ts is None else now_ts)
|
|
241
|
+
|
|
242
|
+
def close(self) -> None:
|
|
243
|
+
with self._lock:
|
|
244
|
+
self._conn.close()
|
|
245
|
+
|
|
246
|
+
def _recover_reserved(self) -> None:
|
|
247
|
+
with self._lock, self._conn:
|
|
248
|
+
rows = self._conn.execute("SELECT * FROM reserved_commands").fetchall()
|
|
249
|
+
for row in rows:
|
|
250
|
+
self._conn.execute(
|
|
251
|
+
"""
|
|
252
|
+
INSERT OR REPLACE INTO queued_commands(
|
|
253
|
+
command_id, source, summary, scope_agent_id, priority, payload_json, dedup_key, created_at
|
|
254
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
255
|
+
""",
|
|
256
|
+
(
|
|
257
|
+
row["command_id"],
|
|
258
|
+
row["source"],
|
|
259
|
+
row["summary"],
|
|
260
|
+
row["scope_agent_id"],
|
|
261
|
+
row["priority"],
|
|
262
|
+
row["payload_json"],
|
|
263
|
+
row["dedup_key"],
|
|
264
|
+
row["created_at"],
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
self._conn.execute("DELETE FROM reserved_commands")
|
|
268
|
+
|
|
269
|
+
def _evict_expired_locked(self, *, now_ts: float) -> int:
|
|
270
|
+
if self._ttl_sec is None or self._ttl_sec <= 0:
|
|
271
|
+
return 0
|
|
272
|
+
cutoff = float(now_ts) - float(self._ttl_sec)
|
|
273
|
+
with self._conn:
|
|
274
|
+
cur = self._conn.execute(
|
|
275
|
+
"DELETE FROM queued_commands WHERE created_at <= ?",
|
|
276
|
+
(cutoff,),
|
|
277
|
+
)
|
|
278
|
+
return int(cur.rowcount or 0)
|
|
279
|
+
|
|
280
|
+
def _evict_over_capacity_locked(self) -> None:
|
|
281
|
+
if self._max_queue_size is None or self._max_queue_size < 0:
|
|
282
|
+
return
|
|
283
|
+
total = int(self._conn.execute("SELECT COUNT(*) FROM queued_commands").fetchone()[0])
|
|
284
|
+
overflow = total - self._max_queue_size
|
|
285
|
+
if overflow <= 0:
|
|
286
|
+
return
|
|
287
|
+
rows = self._conn.execute(
|
|
288
|
+
"""
|
|
289
|
+
SELECT command_id
|
|
290
|
+
FROM queued_commands
|
|
291
|
+
ORDER BY
|
|
292
|
+
CASE priority
|
|
293
|
+
WHEN 'now' THEN 0
|
|
294
|
+
WHEN 'next' THEN 1
|
|
295
|
+
ELSE 2
|
|
296
|
+
END ASC,
|
|
297
|
+
created_at ASC
|
|
298
|
+
"""
|
|
299
|
+
).fetchall()
|
|
300
|
+
keep_ids = [str(item["command_id"]) for item in rows[: self._max_queue_size]]
|
|
301
|
+
if not keep_ids:
|
|
302
|
+
self._conn.execute("DELETE FROM queued_commands")
|
|
303
|
+
return
|
|
304
|
+
placeholders = ",".join("?" for _ in keep_ids)
|
|
305
|
+
self._conn.execute(
|
|
306
|
+
f"DELETE FROM queued_commands WHERE command_id NOT IN ({placeholders})",
|
|
307
|
+
keep_ids,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _row_to_env(row: sqlite3.Row) -> QueuedCommandEnvelope:
|
|
312
|
+
return QueuedCommandEnvelope(
|
|
313
|
+
command_id=str(row["command_id"]),
|
|
314
|
+
source=str(row["source"]),
|
|
315
|
+
summary=str(row["summary"]),
|
|
316
|
+
scope=TaskScope(agent_id=row["scope_agent_id"]),
|
|
317
|
+
priority=QueuePriority(str(row["priority"])),
|
|
318
|
+
payload=json.loads(row["payload_json"] or "{}"),
|
|
319
|
+
dedup_key=row["dedup_key"],
|
|
320
|
+
created_at=float(row["created_at"]),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _to_attachment_message(env: QueuedCommandEnvelope) -> dict:
|
|
325
|
+
text = f"[queued_command][{env.source}] {env.summary}"
|
|
326
|
+
return {
|
|
327
|
+
"role": "user",
|
|
328
|
+
"content": [{"type": "text", "text": text}],
|
|
329
|
+
"metadata": {
|
|
330
|
+
"queued_command": {
|
|
331
|
+
"command_id": env.command_id,
|
|
332
|
+
"source": env.source,
|
|
333
|
+
"priority": env.priority.value,
|
|
334
|
+
"payload": env.payload,
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
|