qwen-sci 0.2.2__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.
- qwen_sci-0.2.2.dist-info/METADATA +492 -0
- qwen_sci-0.2.2.dist-info/RECORD +549 -0
- qwen_sci-0.2.2.dist-info/WHEEL +4 -0
- qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
- qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
- src/1.txt +1 -0
- src/__init__.py +1 -0
- src/__main__.py +5 -0
- src/agents/__init__.py +1 -0
- src/agents/experiment_design_agent/__init__.py +268 -0
- src/agents/experiment_design_agent/artifacts.py +747 -0
- src/agents/experiment_design_agent/cache.py +319 -0
- src/agents/experiment_design_agent/completeness.py +130 -0
- src/agents/experiment_design_agent/contracts.py +771 -0
- src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
- src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
- src/agents/experiment_design_agent/discipline_catalog.py +178 -0
- src/agents/experiment_design_agent/evidence_cards.py +338 -0
- src/agents/experiment_design_agent/evidence_planner.py +452 -0
- src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
- src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
- src/agents/experiment_design_agent/idea_adapter.py +100 -0
- src/agents/experiment_design_agent/idea_intake.py +87 -0
- src/agents/experiment_design_agent/llm_json.py +328 -0
- src/agents/experiment_design_agent/orchestrator.py +1220 -0
- src/agents/experiment_design_agent/reasoning_context.py +183 -0
- src/agents/experiment_design_agent/reasoning_validation.py +546 -0
- src/agents/experiment_design_agent/run.py +481 -0
- src/agents/experiment_design_agent/run_logging.py +292 -0
- src/agents/experiment_design_agent/scope_gate.py +198 -0
- src/agents/experiment_design_agent/study_type_composer.py +1360 -0
- src/agents/experiment_design_agent/survey_evidence.py +1381 -0
- src/agents/experiment_design_agent/template_router.py +195 -0
- src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
- src/agents/idea_agent/__init__.py +1 -0
- src/agents/idea_agent/agent/__init__.py +6 -0
- src/agents/idea_agent/agent/artifacts.py +308 -0
- src/agents/idea_agent/agent/base.py +122 -0
- src/agents/idea_agent/agent/ligagent.py +488 -0
- src/agents/idea_agent/agent/mcts.py +2060 -0
- src/agents/idea_agent/agent/prompts/__init__.py +48 -0
- src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
- src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
- src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
- src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
- src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
- src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
- src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
- src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
- src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
- src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
- src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
- src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
- src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
- src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
- src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
- src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
- src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
- src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
- src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
- src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
- src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
- src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
- src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
- src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
- src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
- src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
- src/agents/idea_agent/example/idea_result.json +900 -0
- src/agents/idea_agent/run.py +276 -0
- src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
- src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
- src/agents/idea_agent/tests/test_idea_debate.py +480 -0
- src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
- src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
- src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
- src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
- src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
- src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
- src/agents/idea_agent/tests/test_result_persistence.py +183 -0
- src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
- src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
- src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
- src/agents/idea_agent/utils/core/__init__.py +1 -0
- src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
- src/agents/idea_agent/utils/core/chat_errors.py +95 -0
- src/agents/idea_agent/utils/core/chat_router.py +130 -0
- src/agents/idea_agent/utils/core/chat_transport.py +188 -0
- src/agents/idea_agent/utils/core/config_loader.py +166 -0
- src/agents/idea_agent/utils/core/json_utils.py +28 -0
- src/agents/idea_agent/utils/core/logger.py +287 -0
- src/agents/idea_agent/utils/core/progress.py +58 -0
- src/agents/idea_agent/utils/core/response_parsing.py +57 -0
- src/agents/idea_agent/utils/core/run_inputs.py +198 -0
- src/agents/idea_agent/utils/mcts/__init__.py +1 -0
- src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
- src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
- src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
- src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
- src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
- src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
- src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
- src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
- src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
- src/agents/idea_agent/utils/papers/__init__.py +1 -0
- src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
- src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
- src/agents/idea_agent/utils/prompting/__init__.py +1 -0
- src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
- src/agents/idea_agent/utils/workflow/__init__.py +1 -0
- src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
- src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
- src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
- src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
- src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
- src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
- src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
- src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
- src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
- src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
- src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
- src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
- src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
- src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
- src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
- src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
- src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
- src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
- src/agents/research_plan_author/__init__.py +50 -0
- src/agents/research_plan_author/artifacts.py +270 -0
- src/agents/research_plan_author/authoring_blueprint.py +1246 -0
- src/agents/research_plan_author/bibtex_renderer.py +252 -0
- src/agents/research_plan_author/contract_repair.py +131 -0
- src/agents/research_plan_author/contracts.py +745 -0
- src/agents/research_plan_author/cross_section_editor.py +232 -0
- src/agents/research_plan_author/document_quality.py +987 -0
- src/agents/research_plan_author/idea_evolution.py +223 -0
- src/agents/research_plan_author/input_loader.py +76 -0
- src/agents/research_plan_author/latex_compiler.py +260 -0
- src/agents/research_plan_author/latex_safety.py +159 -0
- src/agents/research_plan_author/llm_json.py +74 -0
- src/agents/research_plan_author/markdown_renderer.py +275 -0
- src/agents/research_plan_author/pdf_validator.py +149 -0
- src/agents/research_plan_author/render.py +166 -0
- src/agents/research_plan_author/render_artifacts.py +187 -0
- src/agents/research_plan_author/run.py +765 -0
- src/agents/research_plan_author/run_logging.py +209 -0
- src/agents/research_plan_author/section_cache.py +184 -0
- src/agents/research_plan_author/section_composer.py +1764 -0
- src/agents/research_plan_author/section_router.py +209 -0
- src/agents/research_plan_author/semantic_validator.py +254 -0
- src/agents/research_plan_author/source_bundle.py +46 -0
- src/agents/research_plan_author/source_registry.py +208 -0
- src/agents/research_plan_author/survey_source_loader.py +137 -0
- src/agents/research_plan_author/template_adapter.py +173 -0
- src/agents/research_plan_author/template_profile.py +204 -0
- src/agents/research_plan_author/tex_renderer.py +496 -0
- src/agents/research_plan_author/theory_presentation.py +134 -0
- src/agents/research_plan_author/theory_spine.py +703 -0
- src/agents/survey_agent/Dockerfile +54 -0
- src/agents/survey_agent/README.md +350 -0
- src/agents/survey_agent/config/deep_survey.yaml +217 -0
- src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
- src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
- src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
- src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
- src/agents/survey_agent/env_report.md +348 -0
- src/agents/survey_agent/modules/__init__.py +0 -0
- src/agents/survey_agent/modules/code_collector.py +2194 -0
- src/agents/survey_agent/modules/code_report_generator.py +1282 -0
- src/agents/survey_agent/modules/data_manager.py +1713 -0
- src/agents/survey_agent/modules/database.py +167 -0
- src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
- src/agents/survey_agent/modules/fulltext_http.py +134 -0
- src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
- src/agents/survey_agent/modules/judge.py +434 -0
- src/agents/survey_agent/modules/outcome_RAG.py +236 -0
- src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
- src/agents/survey_agent/modules/pe.py +1729 -0
- src/agents/survey_agent/modules/pseudo_creator.py +782 -0
- src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
- src/agents/survey_agent/modules/refine_agent.py +1952 -0
- src/agents/survey_agent/modules/survey_generator.py +5691 -0
- src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
- src/agents/survey_agent/modules/visual_prompts.py +327 -0
- src/agents/survey_agent/modules/work_analyzer.py +2750 -0
- src/agents/survey_agent/modules/work_collector.py +5649 -0
- src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
- src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
- src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
- src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
- src/agents/survey_agent/utils/__init__.py +0 -0
- src/agents/survey_agent/utils/ablation_utils.py +317 -0
- src/agents/survey_agent/utils/api_call.py +2518 -0
- src/agents/survey_agent/utils/chat_utils.py +82 -0
- src/agents/survey_agent/utils/config_utils.py +70 -0
- src/agents/survey_agent/utils/convert_to_md.py +29 -0
- src/agents/survey_agent/utils/err_info.py +24 -0
- src/agents/survey_agent/utils/file_utils.py +176 -0
- src/agents/survey_agent/utils/gpu_utils.py +145 -0
- src/agents/survey_agent/utils/graph_visualizer.py +279 -0
- src/agents/survey_agent/utils/html_utils.py +181 -0
- src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
- src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
- src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
- src/agents/survey_agent/utils/mineru_utils.py +396 -0
- src/agents/survey_agent/utils/paper.py +144 -0
- src/agents/survey_agent/utils/read_yaml.py +15 -0
- src/agents/survey_agent/utils/repo_utils.py +167 -0
- src/agents/survey_agent/utils/rich_logger.py +77 -0
- src/agents/survey_agent/utils/step2v2.py +874 -0
- src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
- src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
- src/agents/survey_agent/utils/utils.py +118 -0
- src/agents/survey_agent/utils/work.py +2 -0
- src/cli.py +1611 -0
- src/config/__init__.py +213 -0
- src/config/default.yaml +1122 -0
- src/harness/LICENSE +21 -0
- src/harness/VENDORED.md +14 -0
- src/harness/src/__init__.py +0 -0
- src/harness/src/openharness/__init__.py +0 -0
- src/harness/src/openharness/__main__.py +6 -0
- src/harness/src/openharness/api/__init__.py +21 -0
- src/harness/src/openharness/api/client.py +281 -0
- src/harness/src/openharness/api/codex_client.py +407 -0
- src/harness/src/openharness/api/copilot_auth.py +240 -0
- src/harness/src/openharness/api/copilot_client.py +134 -0
- src/harness/src/openharness/api/errors.py +19 -0
- src/harness/src/openharness/api/openai_client.py +577 -0
- src/harness/src/openharness/api/provider.py +186 -0
- src/harness/src/openharness/api/registry.py +437 -0
- src/harness/src/openharness/api/usage.py +17 -0
- src/harness/src/openharness/auth/__init__.py +29 -0
- src/harness/src/openharness/auth/external.py +610 -0
- src/harness/src/openharness/auth/flows.py +189 -0
- src/harness/src/openharness/auth/manager.py +482 -0
- src/harness/src/openharness/auth/storage.py +269 -0
- src/harness/src/openharness/autopilot/__init__.py +23 -0
- src/harness/src/openharness/autopilot/service.py +2239 -0
- src/harness/src/openharness/autopilot/types.py +91 -0
- src/harness/src/openharness/bridge/__init__.py +20 -0
- src/harness/src/openharness/bridge/manager.py +106 -0
- src/harness/src/openharness/bridge/session_runner.py +46 -0
- src/harness/src/openharness/bridge/types.py +37 -0
- src/harness/src/openharness/bridge/work_secret.py +41 -0
- src/harness/src/openharness/channels/UPSTREAM +6 -0
- src/harness/src/openharness/channels/__init__.py +22 -0
- src/harness/src/openharness/channels/adapter.py +131 -0
- src/harness/src/openharness/channels/bus/__init__.py +6 -0
- src/harness/src/openharness/channels/bus/events.py +38 -0
- src/harness/src/openharness/channels/bus/queue.py +44 -0
- src/harness/src/openharness/channels/impl/__init__.py +6 -0
- src/harness/src/openharness/channels/impl/base.py +141 -0
- src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
- src/harness/src/openharness/channels/impl/discord.py +311 -0
- src/harness/src/openharness/channels/impl/email.py +410 -0
- src/harness/src/openharness/channels/impl/feishu.py +1342 -0
- src/harness/src/openharness/channels/impl/manager.py +257 -0
- src/harness/src/openharness/channels/impl/matrix.py +700 -0
- src/harness/src/openharness/channels/impl/mochat.py +897 -0
- src/harness/src/openharness/channels/impl/qq.py +141 -0
- src/harness/src/openharness/channels/impl/slack.py +284 -0
- src/harness/src/openharness/channels/impl/telegram.py +525 -0
- src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
- src/harness/src/openharness/cli.py +2551 -0
- src/harness/src/openharness/commands/__init__.py +21 -0
- src/harness/src/openharness/commands/registry.py +2783 -0
- src/harness/src/openharness/config/__init__.py +34 -0
- src/harness/src/openharness/config/paths.py +160 -0
- src/harness/src/openharness/config/schema.py +119 -0
- src/harness/src/openharness/config/settings.py +1104 -0
- src/harness/src/openharness/coordinator/__init__.py +12 -0
- src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
- src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
- src/harness/src/openharness/engine/__init__.py +80 -0
- src/harness/src/openharness/engine/cost_tracker.py +24 -0
- src/harness/src/openharness/engine/messages.py +222 -0
- src/harness/src/openharness/engine/query.py +1188 -0
- src/harness/src/openharness/engine/query_engine.py +313 -0
- src/harness/src/openharness/engine/stream_events.py +90 -0
- src/harness/src/openharness/hooks/__init__.py +50 -0
- src/harness/src/openharness/hooks/events.py +20 -0
- src/harness/src/openharness/hooks/executor.py +242 -0
- src/harness/src/openharness/hooks/hot_reload.py +31 -0
- src/harness/src/openharness/hooks/loader.py +68 -0
- src/harness/src/openharness/hooks/schemas.py +66 -0
- src/harness/src/openharness/hooks/types.py +38 -0
- src/harness/src/openharness/keybindings/__init__.py +14 -0
- src/harness/src/openharness/keybindings/default_bindings.py +11 -0
- src/harness/src/openharness/keybindings/loader.py +22 -0
- src/harness/src/openharness/keybindings/parser.py +18 -0
- src/harness/src/openharness/keybindings/resolver.py +13 -0
- src/harness/src/openharness/mcp/__init__.py +79 -0
- src/harness/src/openharness/mcp/client.py +298 -0
- src/harness/src/openharness/mcp/config.py +16 -0
- src/harness/src/openharness/mcp/types.py +76 -0
- src/harness/src/openharness/memory/__init__.py +25 -0
- src/harness/src/openharness/memory/agent.py +91 -0
- src/harness/src/openharness/memory/manager.py +183 -0
- src/harness/src/openharness/memory/memdir.py +52 -0
- src/harness/src/openharness/memory/migrate.py +137 -0
- src/harness/src/openharness/memory/paths.py +22 -0
- src/harness/src/openharness/memory/relevance.py +145 -0
- src/harness/src/openharness/memory/scan.py +116 -0
- src/harness/src/openharness/memory/schema.py +443 -0
- src/harness/src/openharness/memory/search.py +71 -0
- src/harness/src/openharness/memory/team.py +90 -0
- src/harness/src/openharness/memory/types.py +33 -0
- src/harness/src/openharness/memory/usage.py +153 -0
- src/harness/src/openharness/output_styles/__init__.py +5 -0
- src/harness/src/openharness/output_styles/loader.py +42 -0
- src/harness/src/openharness/permissions/__init__.py +26 -0
- src/harness/src/openharness/permissions/checker.py +200 -0
- src/harness/src/openharness/permissions/modes.py +13 -0
- src/harness/src/openharness/personalization/__init__.py +6 -0
- src/harness/src/openharness/personalization/extractor.py +138 -0
- src/harness/src/openharness/personalization/rules.py +64 -0
- src/harness/src/openharness/personalization/session_hook.py +65 -0
- src/harness/src/openharness/platforms.py +86 -0
- src/harness/src/openharness/plugins/__init__.py +53 -0
- src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
- src/harness/src/openharness/plugins/installer.py +39 -0
- src/harness/src/openharness/plugins/loader.py +730 -0
- src/harness/src/openharness/plugins/schemas.py +24 -0
- src/harness/src/openharness/plugins/types.py +59 -0
- src/harness/src/openharness/prompts/__init__.py +14 -0
- src/harness/src/openharness/prompts/claudemd.py +48 -0
- src/harness/src/openharness/prompts/context.py +187 -0
- src/harness/src/openharness/prompts/environment.py +135 -0
- src/harness/src/openharness/prompts/system_prompt.py +109 -0
- src/harness/src/openharness/sandbox/Dockerfile +6 -0
- src/harness/src/openharness/sandbox/__init__.py +33 -0
- src/harness/src/openharness/sandbox/adapter.py +148 -0
- src/harness/src/openharness/sandbox/docker_backend.py +232 -0
- src/harness/src/openharness/sandbox/docker_image.py +103 -0
- src/harness/src/openharness/sandbox/path_validator.py +37 -0
- src/harness/src/openharness/sandbox/session.py +63 -0
- src/harness/src/openharness/services/__init__.py +30 -0
- src/harness/src/openharness/services/autodream/__init__.py +34 -0
- src/harness/src/openharness/services/autodream/backup.py +104 -0
- src/harness/src/openharness/services/autodream/lock.py +138 -0
- src/harness/src/openharness/services/autodream/prompt.py +128 -0
- src/harness/src/openharness/services/autodream/service.py +313 -0
- src/harness/src/openharness/services/compact/__init__.py +1725 -0
- src/harness/src/openharness/services/cron.py +137 -0
- src/harness/src/openharness/services/cron_scheduler.py +595 -0
- src/harness/src/openharness/services/lsp/__init__.py +216 -0
- src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
- src/harness/src/openharness/services/oauth/__init__.py +0 -0
- src/harness/src/openharness/services/session_backend.py +97 -0
- src/harness/src/openharness/services/session_memory/__init__.py +139 -0
- src/harness/src/openharness/services/session_storage.py +230 -0
- src/harness/src/openharness/services/token_estimation.py +15 -0
- src/harness/src/openharness/services/tool_outputs.py +55 -0
- src/harness/src/openharness/skills/__init__.py +44 -0
- src/harness/src/openharness/skills/_frontmatter.py +97 -0
- src/harness/src/openharness/skills/bundled/__init__.py +75 -0
- src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
- src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
- src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
- src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
- src/harness/src/openharness/skills/bundled/content/review.md +33 -0
- src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
- src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
- src/harness/src/openharness/skills/bundled/content/test.md +38 -0
- src/harness/src/openharness/skills/loader.py +229 -0
- src/harness/src/openharness/skills/registry.py +29 -0
- src/harness/src/openharness/skills/types.py +24 -0
- src/harness/src/openharness/state/__init__.py +6 -0
- src/harness/src/openharness/state/app_state.py +30 -0
- src/harness/src/openharness/state/store.py +40 -0
- src/harness/src/openharness/swarm/__init__.py +70 -0
- src/harness/src/openharness/swarm/in_process.py +693 -0
- src/harness/src/openharness/swarm/lockfile.py +24 -0
- src/harness/src/openharness/swarm/mailbox.py +522 -0
- src/harness/src/openharness/swarm/permission_sync.py +1168 -0
- src/harness/src/openharness/swarm/registry.py +410 -0
- src/harness/src/openharness/swarm/spawn_utils.py +229 -0
- src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
- src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
- src/harness/src/openharness/swarm/types.py +398 -0
- src/harness/src/openharness/swarm/worktree.py +315 -0
- src/harness/src/openharness/tasks/__init__.py +18 -0
- src/harness/src/openharness/tasks/local_agent_task.py +28 -0
- src/harness/src/openharness/tasks/local_shell_task.py +17 -0
- src/harness/src/openharness/tasks/manager.py +475 -0
- src/harness/src/openharness/tasks/stop_task.py +11 -0
- src/harness/src/openharness/tasks/types.py +32 -0
- src/harness/src/openharness/themes/__init__.py +21 -0
- src/harness/src/openharness/themes/builtin.py +89 -0
- src/harness/src/openharness/themes/loader.py +55 -0
- src/harness/src/openharness/themes/schema.py +54 -0
- src/harness/src/openharness/tools/__init__.py +107 -0
- src/harness/src/openharness/tools/agent_tool.py +141 -0
- src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
- src/harness/src/openharness/tools/base.py +80 -0
- src/harness/src/openharness/tools/bash_tool.py +259 -0
- src/harness/src/openharness/tools/brief_tool.py +33 -0
- src/harness/src/openharness/tools/config_tool.py +54 -0
- src/harness/src/openharness/tools/cron_create_tool.py +105 -0
- src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
- src/harness/src/openharness/tools/cron_list_tool.py +69 -0
- src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
- src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
- src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
- src/harness/src/openharness/tools/file_edit_tool.py +95 -0
- src/harness/src/openharness/tools/file_read_tool.py +72 -0
- src/harness/src/openharness/tools/file_write_tool.py +87 -0
- src/harness/src/openharness/tools/glob_tool.py +175 -0
- src/harness/src/openharness/tools/grep_tool.py +374 -0
- src/harness/src/openharness/tools/image_generation_tool.py +447 -0
- src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
- src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
- src/harness/src/openharness/tools/lsp_tool.py +154 -0
- src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
- src/harness/src/openharness/tools/mcp_tool.py +72 -0
- src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
- src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
- src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
- src/harness/src/openharness/tools/send_message_tool.py +58 -0
- src/harness/src/openharness/tools/skill_tool.py +43 -0
- src/harness/src/openharness/tools/sleep_tool.py +32 -0
- src/harness/src/openharness/tools/task_create_tool.py +56 -0
- src/harness/src/openharness/tools/task_get_tool.py +33 -0
- src/harness/src/openharness/tools/task_list_tool.py +35 -0
- src/harness/src/openharness/tools/task_output_tool.py +35 -0
- src/harness/src/openharness/tools/task_stop_tool.py +30 -0
- src/harness/src/openharness/tools/task_update_tool.py +50 -0
- src/harness/src/openharness/tools/team_create_tool.py +31 -0
- src/harness/src/openharness/tools/team_delete_tool.py +30 -0
- src/harness/src/openharness/tools/todo_write_tool.py +46 -0
- src/harness/src/openharness/tools/tool_search_tool.py +38 -0
- src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
- src/harness/src/openharness/tools/web_search_tool.py +119 -0
- src/harness/src/openharness/ui/__init__.py +5 -0
- src/harness/src/openharness/ui/app.py +320 -0
- src/harness/src/openharness/ui/backend_host.py +941 -0
- src/harness/src/openharness/ui/coordinator_drain.py +198 -0
- src/harness/src/openharness/ui/input.py +32 -0
- src/harness/src/openharness/ui/output.py +265 -0
- src/harness/src/openharness/ui/permission_dialog.py +14 -0
- src/harness/src/openharness/ui/protocol.py +247 -0
- src/harness/src/openharness/ui/react_launcher.py +179 -0
- src/harness/src/openharness/ui/runtime.py +799 -0
- src/harness/src/openharness/ui/textual_app.py +495 -0
- src/harness/src/openharness/utils/__init__.py +0 -0
- src/harness/src/openharness/utils/file_lock.py +86 -0
- src/harness/src/openharness/utils/fs.py +98 -0
- src/harness/src/openharness/utils/helpers.py +77 -0
- src/harness/src/openharness/utils/network_guard.py +340 -0
- src/harness/src/openharness/utils/shell.py +147 -0
- src/harness/src/openharness/vim/__init__.py +5 -0
- src/harness/src/openharness/vim/transitions.py +8 -0
- src/harness/src/openharness/voice/__init__.py +7 -0
- src/harness/src/openharness/voice/keyterms.py +10 -0
- src/harness/src/openharness/voice/stream_stt.py +8 -0
- src/harness/src/openharness/voice/voice_mode.py +44 -0
- src/llm/__init__.py +29 -0
- src/llm/image_generation.py +402 -0
- src/llm/provider_registry.py +419 -0
- src/llm/runtime_env.py +64 -0
- src/llm/structured_output.py +131 -0
- src/llm/vision.py +198 -0
- src/memory/__init__.py +2 -0
- src/memory/api/__init__.py +1 -0
- src/memory/api/base_memory_system_api.py +98 -0
- src/memory/api/base_symbolic_memory_system_api.py +190 -0
- src/memory/api/base_vector_memory_system_api.py +116 -0
- src/memory/api/faiss_memory_system_api.py +228 -0
- src/memory/api/slot_process_api.py +827 -0
- src/memory/api/symbolic_memory_system_api.py +572 -0
- src/memory/memory_system/__init__.py +48 -0
- src/memory/memory_system/component_taxonomy.py +430 -0
- src/memory/memory_system/llm.py +437 -0
- src/memory/memory_system/models.py +168 -0
- src/memory/memory_system/user_prompt.py +418 -0
- src/memory/memory_system/utils.py +282 -0
- src/memory/memory_system/vectorstore.py +311 -0
- src/memory/memory_system/working_slot.py +99 -0
- src/pipeline/__init__.py +129 -0
- src/pipeline/contracts.py +531 -0
- src/pipeline/discipline_taxonomy.py +916 -0
- src/pipeline/evidence_coverage_ledger.py +1150 -0
- src/pipeline/evidence_refinement.py +620 -0
- src/pipeline/experiment_to_symbolic.py +137 -0
- src/pipeline/multimodal_evidence/__init__.py +28 -0
- src/pipeline/multimodal_evidence/capabilities.py +107 -0
- src/pipeline/multimodal_evidence/claims.py +135 -0
- src/pipeline/multimodal_evidence/contract.py +291 -0
- src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
- src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
- src/pipeline/multimodal_evidence/manifest.py +254 -0
- src/pipeline/multimodal_evidence/native_analysis.py +479 -0
- src/pipeline/multimodal_evidence/perception.py +277 -0
- src/pipeline/multimodal_evidence/query_binding.py +65 -0
- src/pipeline/multimodal_evidence/rendering.py +120 -0
- src/pipeline/multimodal_evidence/safety.py +42 -0
- src/pipeline/multimodal_evidence/sampling.py +69 -0
- src/pipeline/multimodal_evidence/service.py +148 -0
- src/pipeline/multimodal_evidence/survey_integration.py +460 -0
- src/pipeline/paper_identity.py +52 -0
- src/pipeline/research_design_inventory.py +338 -0
- src/pipeline/research_domain_resolution.py +669 -0
- src/pipeline/research_identity.py +548 -0
- src/pipeline/research_question_contract.py +646 -0
- src/pipeline/retrieval_lanes.py +889 -0
- src/pipeline/run_loop.py +862 -0
- src/pipeline/science_events.py +30 -0
- src/pipeline/science_manifests.py +602 -0
- src/pipeline/science_run.py +918 -0
- src/pipeline/science_stages.py +709 -0
- src/pipeline/science_workflow.py +807 -0
- src/pipeline/sh_cluster_projection.py +306 -0
- src/pipeline/sh_graph_provenance.py +648 -0
- src/pipeline/subhypothesis_decomposition.py +392 -0
- src/pipeline/subhypothesis_evidence.py +856 -0
- src/pipeline/survey_evidence_plan.py +858 -0
- src/pipeline/survey_gap_adjudication.py +647 -0
- src/pipeline/survey_gap_candidates.py +840 -0
- src/pipeline/survey_gap_ledger.py +683 -0
- src/pipeline/survey_gap_triage.py +401 -0
- src/pipeline/survey_handoff_persistence.py +481 -0
- src/pipeline/survey_handoff_projection.py +457 -0
- src/pipeline/survey_idea_handoff.py +1112 -0
- src/pipeline/survey_idea_loader.py +289 -0
- src/research_run_ids.py +11 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Hook execution engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import fnmatch
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import shlex
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from openharness.api.client import ApiMessageCompleteEvent, ApiMessageRequest, SupportsStreamingMessages
|
|
17
|
+
from openharness.engine.messages import ConversationMessage
|
|
18
|
+
from openharness.hooks.events import HookEvent
|
|
19
|
+
from openharness.hooks.loader import HookRegistry
|
|
20
|
+
from openharness.hooks.schemas import (
|
|
21
|
+
AgentHookDefinition,
|
|
22
|
+
CommandHookDefinition,
|
|
23
|
+
HookDefinition,
|
|
24
|
+
HttpHookDefinition,
|
|
25
|
+
PromptHookDefinition,
|
|
26
|
+
)
|
|
27
|
+
from openharness.hooks.types import AggregatedHookResult, HookResult
|
|
28
|
+
from openharness.sandbox import SandboxUnavailableError
|
|
29
|
+
from openharness.utils.shell import create_shell_subprocess
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class HookExecutionContext:
|
|
34
|
+
"""Context passed into hook execution."""
|
|
35
|
+
|
|
36
|
+
cwd: Path
|
|
37
|
+
api_client: SupportsStreamingMessages
|
|
38
|
+
default_model: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class HookExecutor:
|
|
42
|
+
"""Execute hooks for lifecycle events."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, registry: HookRegistry, context: HookExecutionContext) -> None:
|
|
45
|
+
self._registry = registry
|
|
46
|
+
self._context = context
|
|
47
|
+
|
|
48
|
+
def update_registry(self, registry: HookRegistry) -> None:
|
|
49
|
+
"""Replace the active hook registry."""
|
|
50
|
+
self._registry = registry
|
|
51
|
+
|
|
52
|
+
def update_context(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
api_client: SupportsStreamingMessages | None = None,
|
|
56
|
+
default_model: str | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Update the active hook execution context."""
|
|
59
|
+
if api_client is not None:
|
|
60
|
+
self._context.api_client = api_client
|
|
61
|
+
if default_model is not None:
|
|
62
|
+
self._context.default_model = default_model
|
|
63
|
+
|
|
64
|
+
async def execute(self, event: HookEvent, payload: dict[str, Any]) -> AggregatedHookResult:
|
|
65
|
+
"""Execute all matching hooks for an event."""
|
|
66
|
+
results: list[HookResult] = []
|
|
67
|
+
for hook in self._registry.get(event):
|
|
68
|
+
if not _matches_hook(hook, payload):
|
|
69
|
+
continue
|
|
70
|
+
if isinstance(hook, CommandHookDefinition):
|
|
71
|
+
results.append(await self._run_command_hook(hook, event, payload))
|
|
72
|
+
elif isinstance(hook, HttpHookDefinition):
|
|
73
|
+
results.append(await self._run_http_hook(hook, event, payload))
|
|
74
|
+
elif isinstance(hook, PromptHookDefinition):
|
|
75
|
+
results.append(await self._run_prompt_like_hook(hook, event, payload, agent_mode=False))
|
|
76
|
+
elif isinstance(hook, AgentHookDefinition):
|
|
77
|
+
results.append(await self._run_prompt_like_hook(hook, event, payload, agent_mode=True))
|
|
78
|
+
return AggregatedHookResult(results=results)
|
|
79
|
+
|
|
80
|
+
async def _run_command_hook(
|
|
81
|
+
self,
|
|
82
|
+
hook: CommandHookDefinition,
|
|
83
|
+
event: HookEvent,
|
|
84
|
+
payload: dict[str, Any],
|
|
85
|
+
) -> HookResult:
|
|
86
|
+
command = _inject_arguments(hook.command, payload, shell_escape=True)
|
|
87
|
+
try:
|
|
88
|
+
process = await create_shell_subprocess(
|
|
89
|
+
command,
|
|
90
|
+
cwd=self._context.cwd,
|
|
91
|
+
stdout=asyncio.subprocess.PIPE,
|
|
92
|
+
stderr=asyncio.subprocess.PIPE,
|
|
93
|
+
env={
|
|
94
|
+
**os.environ,
|
|
95
|
+
"OPENHARNESS_HOOK_EVENT": event.value,
|
|
96
|
+
"OPENHARNESS_HOOK_PAYLOAD": json.dumps(payload),
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
except SandboxUnavailableError as exc:
|
|
100
|
+
return HookResult(
|
|
101
|
+
hook_type=hook.type,
|
|
102
|
+
success=False,
|
|
103
|
+
blocked=hook.block_on_failure,
|
|
104
|
+
reason=str(exc),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
stdout, stderr = await asyncio.wait_for(
|
|
109
|
+
process.communicate(),
|
|
110
|
+
timeout=hook.timeout_seconds,
|
|
111
|
+
)
|
|
112
|
+
except asyncio.TimeoutError:
|
|
113
|
+
process.kill()
|
|
114
|
+
await process.wait()
|
|
115
|
+
return HookResult(
|
|
116
|
+
hook_type=hook.type,
|
|
117
|
+
success=False,
|
|
118
|
+
blocked=hook.block_on_failure,
|
|
119
|
+
reason=f"command hook timed out after {hook.timeout_seconds}s",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
output = "\n".join(
|
|
123
|
+
part for part in (
|
|
124
|
+
stdout.decode("utf-8", errors="replace").strip(),
|
|
125
|
+
stderr.decode("utf-8", errors="replace").strip(),
|
|
126
|
+
) if part
|
|
127
|
+
)
|
|
128
|
+
success = process.returncode == 0
|
|
129
|
+
return HookResult(
|
|
130
|
+
hook_type=hook.type,
|
|
131
|
+
success=success,
|
|
132
|
+
output=output,
|
|
133
|
+
blocked=hook.block_on_failure and not success,
|
|
134
|
+
reason=output or f"command hook failed with exit code {process.returncode}",
|
|
135
|
+
metadata={"returncode": process.returncode},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def _run_http_hook(
|
|
139
|
+
self,
|
|
140
|
+
hook: HttpHookDefinition,
|
|
141
|
+
event: HookEvent,
|
|
142
|
+
payload: dict[str, Any],
|
|
143
|
+
) -> HookResult:
|
|
144
|
+
try:
|
|
145
|
+
async with httpx.AsyncClient(timeout=hook.timeout_seconds) as client:
|
|
146
|
+
response = await client.post(
|
|
147
|
+
hook.url,
|
|
148
|
+
json={"event": event.value, "payload": payload},
|
|
149
|
+
headers=hook.headers,
|
|
150
|
+
)
|
|
151
|
+
success = response.is_success
|
|
152
|
+
output = response.text
|
|
153
|
+
return HookResult(
|
|
154
|
+
hook_type=hook.type,
|
|
155
|
+
success=success,
|
|
156
|
+
output=output,
|
|
157
|
+
blocked=hook.block_on_failure and not success,
|
|
158
|
+
reason=output or f"http hook returned {response.status_code}",
|
|
159
|
+
metadata={"status_code": response.status_code},
|
|
160
|
+
)
|
|
161
|
+
except Exception as exc:
|
|
162
|
+
return HookResult(
|
|
163
|
+
hook_type=hook.type,
|
|
164
|
+
success=False,
|
|
165
|
+
blocked=hook.block_on_failure,
|
|
166
|
+
reason=str(exc),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
async def _run_prompt_like_hook(
|
|
170
|
+
self,
|
|
171
|
+
hook: PromptHookDefinition | AgentHookDefinition,
|
|
172
|
+
event: HookEvent,
|
|
173
|
+
payload: dict[str, Any],
|
|
174
|
+
*,
|
|
175
|
+
agent_mode: bool,
|
|
176
|
+
) -> HookResult:
|
|
177
|
+
prompt = _inject_arguments(hook.prompt, payload)
|
|
178
|
+
prefix = (
|
|
179
|
+
"You are validating whether a hook condition passes in OpenHarness. "
|
|
180
|
+
"Return strict JSON: {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}."
|
|
181
|
+
)
|
|
182
|
+
if agent_mode:
|
|
183
|
+
prefix += " Be more thorough and reason over the payload before deciding."
|
|
184
|
+
request = ApiMessageRequest(
|
|
185
|
+
model=hook.model or self._context.default_model,
|
|
186
|
+
messages=[ConversationMessage.from_user_text(prompt)],
|
|
187
|
+
system_prompt=prefix,
|
|
188
|
+
max_tokens=512,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
text_chunks: list[str] = []
|
|
192
|
+
final_event: ApiMessageCompleteEvent | None = None
|
|
193
|
+
async for event_item in self._context.api_client.stream_message(request):
|
|
194
|
+
if isinstance(event_item, ApiMessageCompleteEvent):
|
|
195
|
+
final_event = event_item
|
|
196
|
+
else:
|
|
197
|
+
text_chunks.append(event_item.text)
|
|
198
|
+
|
|
199
|
+
text = "".join(text_chunks)
|
|
200
|
+
if final_event is not None and final_event.message.text:
|
|
201
|
+
text = final_event.message.text
|
|
202
|
+
|
|
203
|
+
parsed = _parse_hook_json(text)
|
|
204
|
+
if parsed["ok"]:
|
|
205
|
+
return HookResult(hook_type=hook.type, success=True, output=text)
|
|
206
|
+
return HookResult(
|
|
207
|
+
hook_type=hook.type,
|
|
208
|
+
success=False,
|
|
209
|
+
output=text,
|
|
210
|
+
blocked=hook.block_on_failure,
|
|
211
|
+
reason=parsed.get("reason", "hook rejected the event"),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _matches_hook(hook: HookDefinition, payload: dict[str, Any]) -> bool:
|
|
216
|
+
matcher = getattr(hook, "matcher", None)
|
|
217
|
+
if not matcher:
|
|
218
|
+
return True
|
|
219
|
+
subject = str(payload.get("tool_name") or payload.get("prompt") or payload.get("event") or "")
|
|
220
|
+
return fnmatch.fnmatch(subject, matcher)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _inject_arguments(
|
|
224
|
+
template: str, payload: dict[str, Any], *, shell_escape: bool = False
|
|
225
|
+
) -> str:
|
|
226
|
+
serialized = json.dumps(payload, ensure_ascii=True)
|
|
227
|
+
if shell_escape:
|
|
228
|
+
serialized = shlex.quote(serialized)
|
|
229
|
+
return template.replace("$ARGUMENTS", serialized)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _parse_hook_json(text: str) -> dict[str, Any]:
|
|
233
|
+
try:
|
|
234
|
+
parsed = json.loads(text)
|
|
235
|
+
if isinstance(parsed, dict) and isinstance(parsed.get("ok"), bool):
|
|
236
|
+
return parsed
|
|
237
|
+
except json.JSONDecodeError:
|
|
238
|
+
pass
|
|
239
|
+
lowered = text.strip().lower()
|
|
240
|
+
if lowered in {"ok", "true", "yes"}:
|
|
241
|
+
return {"ok": True}
|
|
242
|
+
return {"ok": False, "reason": text.strip() or "hook returned invalid JSON"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Best-effort hot reloading for settings-backed hooks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from openharness.config import load_settings
|
|
8
|
+
from openharness.hooks.loader import HookRegistry, load_hook_registry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HookReloader:
|
|
12
|
+
"""Reload hook definitions when the settings file changes."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, settings_path: Path) -> None:
|
|
15
|
+
self._settings_path = settings_path
|
|
16
|
+
self._last_mtime_ns = -1
|
|
17
|
+
self._registry = HookRegistry()
|
|
18
|
+
|
|
19
|
+
def current_registry(self) -> HookRegistry:
|
|
20
|
+
"""Return the latest registry, reloading if needed."""
|
|
21
|
+
try:
|
|
22
|
+
stat = self._settings_path.stat()
|
|
23
|
+
except FileNotFoundError:
|
|
24
|
+
self._registry = HookRegistry()
|
|
25
|
+
self._last_mtime_ns = -1
|
|
26
|
+
return self._registry
|
|
27
|
+
|
|
28
|
+
if stat.st_mtime_ns != self._last_mtime_ns:
|
|
29
|
+
self._last_mtime_ns = stat.st_mtime_ns
|
|
30
|
+
self._registry = load_hook_registry(load_settings(self._settings_path))
|
|
31
|
+
return self._registry
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Load hooks from settings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from openharness.hooks.events import HookEvent
|
|
7
|
+
from openharness.hooks.schemas import HookDefinition
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HookRegistry:
|
|
11
|
+
"""Store hooks grouped by event."""
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self._hooks: dict[HookEvent, list[HookDefinition]] = defaultdict(list)
|
|
15
|
+
|
|
16
|
+
def register(self, event: HookEvent, hook: HookDefinition) -> None:
|
|
17
|
+
"""Register one hook."""
|
|
18
|
+
self._hooks[event].append(hook)
|
|
19
|
+
|
|
20
|
+
def get(self, event: HookEvent) -> list[HookDefinition]:
|
|
21
|
+
"""Return hooks registered for an event, ordered by priority.
|
|
22
|
+
|
|
23
|
+
Hooks with a higher ``priority`` run first. ``sorted`` is stable, so
|
|
24
|
+
hooks sharing the same priority keep their registration order.
|
|
25
|
+
"""
|
|
26
|
+
hooks = self._hooks.get(event, [])
|
|
27
|
+
return sorted(hooks, key=lambda hook: -getattr(hook, "priority", 0))
|
|
28
|
+
|
|
29
|
+
def summary(self) -> str:
|
|
30
|
+
"""Return a human-readable hook summary."""
|
|
31
|
+
lines: list[str] = []
|
|
32
|
+
for event in HookEvent:
|
|
33
|
+
hooks = self.get(event)
|
|
34
|
+
if not hooks:
|
|
35
|
+
continue
|
|
36
|
+
lines.append(f"{event.value}:")
|
|
37
|
+
for hook in hooks:
|
|
38
|
+
matcher = getattr(hook, "matcher", None)
|
|
39
|
+
detail = getattr(hook, "command", None) or getattr(hook, "prompt", None) or getattr(hook, "url", None) or ""
|
|
40
|
+
suffix = f" matcher={matcher}" if matcher else ""
|
|
41
|
+
priority = getattr(hook, "priority", 0)
|
|
42
|
+
if priority:
|
|
43
|
+
suffix += f" priority={priority}"
|
|
44
|
+
lines.append(f" - {hook.type}{suffix}: {detail}")
|
|
45
|
+
return "\n".join(lines)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_hook_registry(settings, plugins=None) -> HookRegistry:
|
|
49
|
+
"""Load hooks from the current settings object."""
|
|
50
|
+
registry = HookRegistry()
|
|
51
|
+
for raw_event, hooks in settings.hooks.items():
|
|
52
|
+
try:
|
|
53
|
+
event = HookEvent(raw_event)
|
|
54
|
+
except ValueError:
|
|
55
|
+
continue
|
|
56
|
+
for hook in hooks:
|
|
57
|
+
registry.register(event, hook)
|
|
58
|
+
for plugin in plugins or []:
|
|
59
|
+
if not plugin.enabled:
|
|
60
|
+
continue
|
|
61
|
+
for raw_event, hooks in plugin.hooks.items():
|
|
62
|
+
try:
|
|
63
|
+
event = HookEvent(raw_event)
|
|
64
|
+
except ValueError:
|
|
65
|
+
continue
|
|
66
|
+
for hook in hooks:
|
|
67
|
+
registry.register(event, hook)
|
|
68
|
+
return registry
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Hook configuration schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CommandHookDefinition(BaseModel):
|
|
11
|
+
"""A hook that executes a shell command."""
|
|
12
|
+
|
|
13
|
+
type: Literal["command"] = "command"
|
|
14
|
+
command: str
|
|
15
|
+
timeout_seconds: int = Field(default=30, ge=1, le=600)
|
|
16
|
+
matcher: str | None = None
|
|
17
|
+
block_on_failure: bool = False
|
|
18
|
+
priority: int = Field(default=0)
|
|
19
|
+
"""Higher priority runs first within an event; ties keep registration order."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PromptHookDefinition(BaseModel):
|
|
23
|
+
"""A hook that asks the model to validate a condition."""
|
|
24
|
+
|
|
25
|
+
type: Literal["prompt"] = "prompt"
|
|
26
|
+
prompt: str
|
|
27
|
+
model: str | None = None
|
|
28
|
+
timeout_seconds: int = Field(default=30, ge=1, le=600)
|
|
29
|
+
matcher: str | None = None
|
|
30
|
+
block_on_failure: bool = True
|
|
31
|
+
priority: int = Field(default=0)
|
|
32
|
+
"""Higher priority runs first within an event; ties keep registration order."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class HttpHookDefinition(BaseModel):
|
|
36
|
+
"""A hook that POSTs the event payload to an HTTP endpoint."""
|
|
37
|
+
|
|
38
|
+
type: Literal["http"] = "http"
|
|
39
|
+
url: str
|
|
40
|
+
headers: dict[str, str] = Field(default_factory=dict)
|
|
41
|
+
timeout_seconds: int = Field(default=30, ge=1, le=600)
|
|
42
|
+
matcher: str | None = None
|
|
43
|
+
block_on_failure: bool = False
|
|
44
|
+
priority: int = Field(default=0)
|
|
45
|
+
"""Higher priority runs first within an event; ties keep registration order."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AgentHookDefinition(BaseModel):
|
|
49
|
+
"""A hook that performs a deeper model-based validation."""
|
|
50
|
+
|
|
51
|
+
type: Literal["agent"] = "agent"
|
|
52
|
+
prompt: str
|
|
53
|
+
model: str | None = None
|
|
54
|
+
timeout_seconds: int = Field(default=60, ge=1, le=1200)
|
|
55
|
+
matcher: str | None = None
|
|
56
|
+
block_on_failure: bool = True
|
|
57
|
+
priority: int = Field(default=0)
|
|
58
|
+
"""Higher priority runs first within an event; ties keep registration order."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
HookDefinition = (
|
|
62
|
+
CommandHookDefinition
|
|
63
|
+
| PromptHookDefinition
|
|
64
|
+
| HttpHookDefinition
|
|
65
|
+
| AgentHookDefinition
|
|
66
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Runtime hook result types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class HookResult:
|
|
11
|
+
"""Result from a single hook execution."""
|
|
12
|
+
|
|
13
|
+
hook_type: str
|
|
14
|
+
success: bool
|
|
15
|
+
output: str = ""
|
|
16
|
+
blocked: bool = False
|
|
17
|
+
reason: str = ""
|
|
18
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class AggregatedHookResult:
|
|
23
|
+
"""Aggregated result for a hook event."""
|
|
24
|
+
|
|
25
|
+
results: list[HookResult] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def blocked(self) -> bool:
|
|
29
|
+
"""Return whether any hook blocked continuation."""
|
|
30
|
+
return any(result.blocked for result in self.results)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def reason(self) -> str:
|
|
34
|
+
"""Return the first blocking reason, if any."""
|
|
35
|
+
for result in self.results:
|
|
36
|
+
if result.blocked:
|
|
37
|
+
return result.reason or result.output
|
|
38
|
+
return ""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Keybindings exports."""
|
|
2
|
+
|
|
3
|
+
from openharness.keybindings.default_bindings import DEFAULT_KEYBINDINGS
|
|
4
|
+
from openharness.keybindings.loader import get_keybindings_path, load_keybindings
|
|
5
|
+
from openharness.keybindings.parser import parse_keybindings
|
|
6
|
+
from openharness.keybindings.resolver import resolve_keybindings
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DEFAULT_KEYBINDINGS",
|
|
10
|
+
"get_keybindings_path",
|
|
11
|
+
"load_keybindings",
|
|
12
|
+
"parse_keybindings",
|
|
13
|
+
"resolve_keybindings",
|
|
14
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Load keybindings from config."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from openharness.config.paths import get_config_dir
|
|
8
|
+
from openharness.keybindings.parser import parse_keybindings
|
|
9
|
+
from openharness.keybindings.resolver import resolve_keybindings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_keybindings_path() -> Path:
|
|
13
|
+
"""Return the user keybindings path."""
|
|
14
|
+
return get_config_dir() / "keybindings.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_keybindings() -> dict[str, str]:
|
|
18
|
+
"""Load and merge keybindings."""
|
|
19
|
+
path = get_keybindings_path()
|
|
20
|
+
if not path.exists():
|
|
21
|
+
return resolve_keybindings()
|
|
22
|
+
return resolve_keybindings(parse_keybindings(path.read_text(encoding="utf-8")))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Keybinding file parsing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_keybindings(text: str) -> dict[str, str]:
|
|
9
|
+
"""Parse a JSON keybinding mapping."""
|
|
10
|
+
data = json.loads(text)
|
|
11
|
+
if not isinstance(data, dict):
|
|
12
|
+
raise ValueError("keybindings file must be a JSON object")
|
|
13
|
+
parsed: dict[str, str] = {}
|
|
14
|
+
for key, value in data.items():
|
|
15
|
+
if not isinstance(key, str) or not isinstance(value, str):
|
|
16
|
+
raise ValueError("keybindings keys and values must be strings")
|
|
17
|
+
parsed[key] = value
|
|
18
|
+
return parsed
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Keybinding resolution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from openharness.keybindings.default_bindings import DEFAULT_KEYBINDINGS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def resolve_keybindings(overrides: dict[str, str] | None = None) -> dict[str, str]:
|
|
9
|
+
"""Merge user overrides over the default keybindings."""
|
|
10
|
+
resolved = dict(DEFAULT_KEYBINDINGS)
|
|
11
|
+
if overrides:
|
|
12
|
+
resolved.update(overrides)
|
|
13
|
+
return resolved
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""MCP exports."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
8
|
+
from openharness.mcp.client import McpClientManager, McpServerNotConnectedError
|
|
9
|
+
from openharness.mcp.types import (
|
|
10
|
+
McpConnectionStatus,
|
|
11
|
+
McpHttpServerConfig,
|
|
12
|
+
McpJsonConfig,
|
|
13
|
+
McpResourceInfo,
|
|
14
|
+
McpServerConfig,
|
|
15
|
+
McpStdioServerConfig,
|
|
16
|
+
McpToolInfo,
|
|
17
|
+
McpWebSocketServerConfig,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"McpClientManager",
|
|
22
|
+
"McpConnectionStatus",
|
|
23
|
+
"McpServerNotConnectedError",
|
|
24
|
+
"McpHttpServerConfig",
|
|
25
|
+
"McpJsonConfig",
|
|
26
|
+
"McpResourceInfo",
|
|
27
|
+
"McpServerConfig",
|
|
28
|
+
"McpStdioServerConfig",
|
|
29
|
+
"McpToolInfo",
|
|
30
|
+
"McpWebSocketServerConfig",
|
|
31
|
+
"load_mcp_server_configs",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def __getattr__(name: str):
|
|
36
|
+
if name == "McpClientManager":
|
|
37
|
+
from openharness.mcp.client import McpClientManager
|
|
38
|
+
|
|
39
|
+
return McpClientManager
|
|
40
|
+
if name == "McpServerNotConnectedError":
|
|
41
|
+
from openharness.mcp.client import McpServerNotConnectedError
|
|
42
|
+
|
|
43
|
+
return McpServerNotConnectedError
|
|
44
|
+
if name == "load_mcp_server_configs":
|
|
45
|
+
from openharness.mcp.config import load_mcp_server_configs
|
|
46
|
+
|
|
47
|
+
return load_mcp_server_configs
|
|
48
|
+
if name in {
|
|
49
|
+
"McpConnectionStatus",
|
|
50
|
+
"McpHttpServerConfig",
|
|
51
|
+
"McpJsonConfig",
|
|
52
|
+
"McpResourceInfo",
|
|
53
|
+
"McpServerConfig",
|
|
54
|
+
"McpStdioServerConfig",
|
|
55
|
+
"McpToolInfo",
|
|
56
|
+
"McpWebSocketServerConfig",
|
|
57
|
+
}:
|
|
58
|
+
from openharness.mcp.types import (
|
|
59
|
+
McpConnectionStatus,
|
|
60
|
+
McpHttpServerConfig,
|
|
61
|
+
McpJsonConfig,
|
|
62
|
+
McpResourceInfo,
|
|
63
|
+
McpServerConfig,
|
|
64
|
+
McpStdioServerConfig,
|
|
65
|
+
McpToolInfo,
|
|
66
|
+
McpWebSocketServerConfig,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
"McpConnectionStatus": McpConnectionStatus,
|
|
71
|
+
"McpHttpServerConfig": McpHttpServerConfig,
|
|
72
|
+
"McpJsonConfig": McpJsonConfig,
|
|
73
|
+
"McpResourceInfo": McpResourceInfo,
|
|
74
|
+
"McpServerConfig": McpServerConfig,
|
|
75
|
+
"McpStdioServerConfig": McpStdioServerConfig,
|
|
76
|
+
"McpToolInfo": McpToolInfo,
|
|
77
|
+
"McpWebSocketServerConfig": McpWebSocketServerConfig,
|
|
78
|
+
}[name]
|
|
79
|
+
raise AttributeError(name)
|