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.
Files changed (549) hide show
  1. qwen_sci-0.2.2.dist-info/METADATA +492 -0
  2. qwen_sci-0.2.2.dist-info/RECORD +549 -0
  3. qwen_sci-0.2.2.dist-info/WHEEL +4 -0
  4. qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
  5. qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
  6. src/1.txt +1 -0
  7. src/__init__.py +1 -0
  8. src/__main__.py +5 -0
  9. src/agents/__init__.py +1 -0
  10. src/agents/experiment_design_agent/__init__.py +268 -0
  11. src/agents/experiment_design_agent/artifacts.py +747 -0
  12. src/agents/experiment_design_agent/cache.py +319 -0
  13. src/agents/experiment_design_agent/completeness.py +130 -0
  14. src/agents/experiment_design_agent/contracts.py +771 -0
  15. src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
  16. src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
  17. src/agents/experiment_design_agent/discipline_catalog.py +178 -0
  18. src/agents/experiment_design_agent/evidence_cards.py +338 -0
  19. src/agents/experiment_design_agent/evidence_planner.py +452 -0
  20. src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
  21. src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
  22. src/agents/experiment_design_agent/idea_adapter.py +100 -0
  23. src/agents/experiment_design_agent/idea_intake.py +87 -0
  24. src/agents/experiment_design_agent/llm_json.py +328 -0
  25. src/agents/experiment_design_agent/orchestrator.py +1220 -0
  26. src/agents/experiment_design_agent/reasoning_context.py +183 -0
  27. src/agents/experiment_design_agent/reasoning_validation.py +546 -0
  28. src/agents/experiment_design_agent/run.py +481 -0
  29. src/agents/experiment_design_agent/run_logging.py +292 -0
  30. src/agents/experiment_design_agent/scope_gate.py +198 -0
  31. src/agents/experiment_design_agent/study_type_composer.py +1360 -0
  32. src/agents/experiment_design_agent/survey_evidence.py +1381 -0
  33. src/agents/experiment_design_agent/template_router.py +195 -0
  34. src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
  35. src/agents/idea_agent/__init__.py +1 -0
  36. src/agents/idea_agent/agent/__init__.py +6 -0
  37. src/agents/idea_agent/agent/artifacts.py +308 -0
  38. src/agents/idea_agent/agent/base.py +122 -0
  39. src/agents/idea_agent/agent/ligagent.py +488 -0
  40. src/agents/idea_agent/agent/mcts.py +2060 -0
  41. src/agents/idea_agent/agent/prompts/__init__.py +48 -0
  42. src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
  43. src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
  44. src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
  45. src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
  46. src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
  47. src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
  48. src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
  49. src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
  50. src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
  51. src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
  52. src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
  53. src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
  54. src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
  55. src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
  56. src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
  57. src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
  58. src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
  59. src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
  60. src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
  61. src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
  62. src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
  63. src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
  64. src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
  65. src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
  66. src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
  67. src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
  68. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
  69. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
  70. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
  71. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
  72. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
  73. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
  74. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
  75. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
  76. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
  77. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
  78. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
  79. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
  80. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
  81. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
  82. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
  83. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
  84. src/agents/idea_agent/example/idea_result.json +900 -0
  85. src/agents/idea_agent/run.py +276 -0
  86. src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
  87. src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
  88. src/agents/idea_agent/tests/test_idea_debate.py +480 -0
  89. src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
  90. src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
  91. src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
  92. src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
  93. src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
  94. src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
  95. src/agents/idea_agent/tests/test_result_persistence.py +183 -0
  96. src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
  97. src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
  98. src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
  99. src/agents/idea_agent/utils/core/__init__.py +1 -0
  100. src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
  101. src/agents/idea_agent/utils/core/chat_errors.py +95 -0
  102. src/agents/idea_agent/utils/core/chat_router.py +130 -0
  103. src/agents/idea_agent/utils/core/chat_transport.py +188 -0
  104. src/agents/idea_agent/utils/core/config_loader.py +166 -0
  105. src/agents/idea_agent/utils/core/json_utils.py +28 -0
  106. src/agents/idea_agent/utils/core/logger.py +287 -0
  107. src/agents/idea_agent/utils/core/progress.py +58 -0
  108. src/agents/idea_agent/utils/core/response_parsing.py +57 -0
  109. src/agents/idea_agent/utils/core/run_inputs.py +198 -0
  110. src/agents/idea_agent/utils/mcts/__init__.py +1 -0
  111. src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
  112. src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
  113. src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
  114. src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
  115. src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
  116. src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
  117. src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
  118. src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
  119. src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
  120. src/agents/idea_agent/utils/papers/__init__.py +1 -0
  121. src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
  122. src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
  123. src/agents/idea_agent/utils/prompting/__init__.py +1 -0
  124. src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
  125. src/agents/idea_agent/utils/workflow/__init__.py +1 -0
  126. src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
  127. src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
  128. src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
  129. src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
  130. src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
  131. src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
  132. src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
  133. src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
  134. src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
  135. src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
  136. src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
  137. src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
  138. src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
  139. src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
  140. src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
  141. src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
  142. src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
  143. src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
  144. src/agents/research_plan_author/__init__.py +50 -0
  145. src/agents/research_plan_author/artifacts.py +270 -0
  146. src/agents/research_plan_author/authoring_blueprint.py +1246 -0
  147. src/agents/research_plan_author/bibtex_renderer.py +252 -0
  148. src/agents/research_plan_author/contract_repair.py +131 -0
  149. src/agents/research_plan_author/contracts.py +745 -0
  150. src/agents/research_plan_author/cross_section_editor.py +232 -0
  151. src/agents/research_plan_author/document_quality.py +987 -0
  152. src/agents/research_plan_author/idea_evolution.py +223 -0
  153. src/agents/research_plan_author/input_loader.py +76 -0
  154. src/agents/research_plan_author/latex_compiler.py +260 -0
  155. src/agents/research_plan_author/latex_safety.py +159 -0
  156. src/agents/research_plan_author/llm_json.py +74 -0
  157. src/agents/research_plan_author/markdown_renderer.py +275 -0
  158. src/agents/research_plan_author/pdf_validator.py +149 -0
  159. src/agents/research_plan_author/render.py +166 -0
  160. src/agents/research_plan_author/render_artifacts.py +187 -0
  161. src/agents/research_plan_author/run.py +765 -0
  162. src/agents/research_plan_author/run_logging.py +209 -0
  163. src/agents/research_plan_author/section_cache.py +184 -0
  164. src/agents/research_plan_author/section_composer.py +1764 -0
  165. src/agents/research_plan_author/section_router.py +209 -0
  166. src/agents/research_plan_author/semantic_validator.py +254 -0
  167. src/agents/research_plan_author/source_bundle.py +46 -0
  168. src/agents/research_plan_author/source_registry.py +208 -0
  169. src/agents/research_plan_author/survey_source_loader.py +137 -0
  170. src/agents/research_plan_author/template_adapter.py +173 -0
  171. src/agents/research_plan_author/template_profile.py +204 -0
  172. src/agents/research_plan_author/tex_renderer.py +496 -0
  173. src/agents/research_plan_author/theory_presentation.py +134 -0
  174. src/agents/research_plan_author/theory_spine.py +703 -0
  175. src/agents/survey_agent/Dockerfile +54 -0
  176. src/agents/survey_agent/README.md +350 -0
  177. src/agents/survey_agent/config/deep_survey.yaml +217 -0
  178. src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
  179. src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
  180. src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
  181. src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
  182. src/agents/survey_agent/env_report.md +348 -0
  183. src/agents/survey_agent/modules/__init__.py +0 -0
  184. src/agents/survey_agent/modules/code_collector.py +2194 -0
  185. src/agents/survey_agent/modules/code_report_generator.py +1282 -0
  186. src/agents/survey_agent/modules/data_manager.py +1713 -0
  187. src/agents/survey_agent/modules/database.py +167 -0
  188. src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
  189. src/agents/survey_agent/modules/fulltext_http.py +134 -0
  190. src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
  191. src/agents/survey_agent/modules/judge.py +434 -0
  192. src/agents/survey_agent/modules/outcome_RAG.py +236 -0
  193. src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
  194. src/agents/survey_agent/modules/pe.py +1729 -0
  195. src/agents/survey_agent/modules/pseudo_creator.py +782 -0
  196. src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
  197. src/agents/survey_agent/modules/refine_agent.py +1952 -0
  198. src/agents/survey_agent/modules/survey_generator.py +5691 -0
  199. src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
  200. src/agents/survey_agent/modules/visual_prompts.py +327 -0
  201. src/agents/survey_agent/modules/work_analyzer.py +2750 -0
  202. src/agents/survey_agent/modules/work_collector.py +5649 -0
  203. src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
  204. src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
  205. src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
  206. src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
  207. src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
  208. src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
  209. src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
  210. src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
  211. src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
  212. src/agents/survey_agent/utils/__init__.py +0 -0
  213. src/agents/survey_agent/utils/ablation_utils.py +317 -0
  214. src/agents/survey_agent/utils/api_call.py +2518 -0
  215. src/agents/survey_agent/utils/chat_utils.py +82 -0
  216. src/agents/survey_agent/utils/config_utils.py +70 -0
  217. src/agents/survey_agent/utils/convert_to_md.py +29 -0
  218. src/agents/survey_agent/utils/err_info.py +24 -0
  219. src/agents/survey_agent/utils/file_utils.py +176 -0
  220. src/agents/survey_agent/utils/gpu_utils.py +145 -0
  221. src/agents/survey_agent/utils/graph_visualizer.py +279 -0
  222. src/agents/survey_agent/utils/html_utils.py +181 -0
  223. src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
  224. src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
  225. src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
  226. src/agents/survey_agent/utils/mineru_utils.py +396 -0
  227. src/agents/survey_agent/utils/paper.py +144 -0
  228. src/agents/survey_agent/utils/read_yaml.py +15 -0
  229. src/agents/survey_agent/utils/repo_utils.py +167 -0
  230. src/agents/survey_agent/utils/rich_logger.py +77 -0
  231. src/agents/survey_agent/utils/step2v2.py +874 -0
  232. src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
  233. src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
  234. src/agents/survey_agent/utils/utils.py +118 -0
  235. src/agents/survey_agent/utils/work.py +2 -0
  236. src/cli.py +1611 -0
  237. src/config/__init__.py +213 -0
  238. src/config/default.yaml +1122 -0
  239. src/harness/LICENSE +21 -0
  240. src/harness/VENDORED.md +14 -0
  241. src/harness/src/__init__.py +0 -0
  242. src/harness/src/openharness/__init__.py +0 -0
  243. src/harness/src/openharness/__main__.py +6 -0
  244. src/harness/src/openharness/api/__init__.py +21 -0
  245. src/harness/src/openharness/api/client.py +281 -0
  246. src/harness/src/openharness/api/codex_client.py +407 -0
  247. src/harness/src/openharness/api/copilot_auth.py +240 -0
  248. src/harness/src/openharness/api/copilot_client.py +134 -0
  249. src/harness/src/openharness/api/errors.py +19 -0
  250. src/harness/src/openharness/api/openai_client.py +577 -0
  251. src/harness/src/openharness/api/provider.py +186 -0
  252. src/harness/src/openharness/api/registry.py +437 -0
  253. src/harness/src/openharness/api/usage.py +17 -0
  254. src/harness/src/openharness/auth/__init__.py +29 -0
  255. src/harness/src/openharness/auth/external.py +610 -0
  256. src/harness/src/openharness/auth/flows.py +189 -0
  257. src/harness/src/openharness/auth/manager.py +482 -0
  258. src/harness/src/openharness/auth/storage.py +269 -0
  259. src/harness/src/openharness/autopilot/__init__.py +23 -0
  260. src/harness/src/openharness/autopilot/service.py +2239 -0
  261. src/harness/src/openharness/autopilot/types.py +91 -0
  262. src/harness/src/openharness/bridge/__init__.py +20 -0
  263. src/harness/src/openharness/bridge/manager.py +106 -0
  264. src/harness/src/openharness/bridge/session_runner.py +46 -0
  265. src/harness/src/openharness/bridge/types.py +37 -0
  266. src/harness/src/openharness/bridge/work_secret.py +41 -0
  267. src/harness/src/openharness/channels/UPSTREAM +6 -0
  268. src/harness/src/openharness/channels/__init__.py +22 -0
  269. src/harness/src/openharness/channels/adapter.py +131 -0
  270. src/harness/src/openharness/channels/bus/__init__.py +6 -0
  271. src/harness/src/openharness/channels/bus/events.py +38 -0
  272. src/harness/src/openharness/channels/bus/queue.py +44 -0
  273. src/harness/src/openharness/channels/impl/__init__.py +6 -0
  274. src/harness/src/openharness/channels/impl/base.py +141 -0
  275. src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
  276. src/harness/src/openharness/channels/impl/discord.py +311 -0
  277. src/harness/src/openharness/channels/impl/email.py +410 -0
  278. src/harness/src/openharness/channels/impl/feishu.py +1342 -0
  279. src/harness/src/openharness/channels/impl/manager.py +257 -0
  280. src/harness/src/openharness/channels/impl/matrix.py +700 -0
  281. src/harness/src/openharness/channels/impl/mochat.py +897 -0
  282. src/harness/src/openharness/channels/impl/qq.py +141 -0
  283. src/harness/src/openharness/channels/impl/slack.py +284 -0
  284. src/harness/src/openharness/channels/impl/telegram.py +525 -0
  285. src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
  286. src/harness/src/openharness/cli.py +2551 -0
  287. src/harness/src/openharness/commands/__init__.py +21 -0
  288. src/harness/src/openharness/commands/registry.py +2783 -0
  289. src/harness/src/openharness/config/__init__.py +34 -0
  290. src/harness/src/openharness/config/paths.py +160 -0
  291. src/harness/src/openharness/config/schema.py +119 -0
  292. src/harness/src/openharness/config/settings.py +1104 -0
  293. src/harness/src/openharness/coordinator/__init__.py +12 -0
  294. src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
  295. src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
  296. src/harness/src/openharness/engine/__init__.py +80 -0
  297. src/harness/src/openharness/engine/cost_tracker.py +24 -0
  298. src/harness/src/openharness/engine/messages.py +222 -0
  299. src/harness/src/openharness/engine/query.py +1188 -0
  300. src/harness/src/openharness/engine/query_engine.py +313 -0
  301. src/harness/src/openharness/engine/stream_events.py +90 -0
  302. src/harness/src/openharness/hooks/__init__.py +50 -0
  303. src/harness/src/openharness/hooks/events.py +20 -0
  304. src/harness/src/openharness/hooks/executor.py +242 -0
  305. src/harness/src/openharness/hooks/hot_reload.py +31 -0
  306. src/harness/src/openharness/hooks/loader.py +68 -0
  307. src/harness/src/openharness/hooks/schemas.py +66 -0
  308. src/harness/src/openharness/hooks/types.py +38 -0
  309. src/harness/src/openharness/keybindings/__init__.py +14 -0
  310. src/harness/src/openharness/keybindings/default_bindings.py +11 -0
  311. src/harness/src/openharness/keybindings/loader.py +22 -0
  312. src/harness/src/openharness/keybindings/parser.py +18 -0
  313. src/harness/src/openharness/keybindings/resolver.py +13 -0
  314. src/harness/src/openharness/mcp/__init__.py +79 -0
  315. src/harness/src/openharness/mcp/client.py +298 -0
  316. src/harness/src/openharness/mcp/config.py +16 -0
  317. src/harness/src/openharness/mcp/types.py +76 -0
  318. src/harness/src/openharness/memory/__init__.py +25 -0
  319. src/harness/src/openharness/memory/agent.py +91 -0
  320. src/harness/src/openharness/memory/manager.py +183 -0
  321. src/harness/src/openharness/memory/memdir.py +52 -0
  322. src/harness/src/openharness/memory/migrate.py +137 -0
  323. src/harness/src/openharness/memory/paths.py +22 -0
  324. src/harness/src/openharness/memory/relevance.py +145 -0
  325. src/harness/src/openharness/memory/scan.py +116 -0
  326. src/harness/src/openharness/memory/schema.py +443 -0
  327. src/harness/src/openharness/memory/search.py +71 -0
  328. src/harness/src/openharness/memory/team.py +90 -0
  329. src/harness/src/openharness/memory/types.py +33 -0
  330. src/harness/src/openharness/memory/usage.py +153 -0
  331. src/harness/src/openharness/output_styles/__init__.py +5 -0
  332. src/harness/src/openharness/output_styles/loader.py +42 -0
  333. src/harness/src/openharness/permissions/__init__.py +26 -0
  334. src/harness/src/openharness/permissions/checker.py +200 -0
  335. src/harness/src/openharness/permissions/modes.py +13 -0
  336. src/harness/src/openharness/personalization/__init__.py +6 -0
  337. src/harness/src/openharness/personalization/extractor.py +138 -0
  338. src/harness/src/openharness/personalization/rules.py +64 -0
  339. src/harness/src/openharness/personalization/session_hook.py +65 -0
  340. src/harness/src/openharness/platforms.py +86 -0
  341. src/harness/src/openharness/plugins/__init__.py +53 -0
  342. src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
  343. src/harness/src/openharness/plugins/installer.py +39 -0
  344. src/harness/src/openharness/plugins/loader.py +730 -0
  345. src/harness/src/openharness/plugins/schemas.py +24 -0
  346. src/harness/src/openharness/plugins/types.py +59 -0
  347. src/harness/src/openharness/prompts/__init__.py +14 -0
  348. src/harness/src/openharness/prompts/claudemd.py +48 -0
  349. src/harness/src/openharness/prompts/context.py +187 -0
  350. src/harness/src/openharness/prompts/environment.py +135 -0
  351. src/harness/src/openharness/prompts/system_prompt.py +109 -0
  352. src/harness/src/openharness/sandbox/Dockerfile +6 -0
  353. src/harness/src/openharness/sandbox/__init__.py +33 -0
  354. src/harness/src/openharness/sandbox/adapter.py +148 -0
  355. src/harness/src/openharness/sandbox/docker_backend.py +232 -0
  356. src/harness/src/openharness/sandbox/docker_image.py +103 -0
  357. src/harness/src/openharness/sandbox/path_validator.py +37 -0
  358. src/harness/src/openharness/sandbox/session.py +63 -0
  359. src/harness/src/openharness/services/__init__.py +30 -0
  360. src/harness/src/openharness/services/autodream/__init__.py +34 -0
  361. src/harness/src/openharness/services/autodream/backup.py +104 -0
  362. src/harness/src/openharness/services/autodream/lock.py +138 -0
  363. src/harness/src/openharness/services/autodream/prompt.py +128 -0
  364. src/harness/src/openharness/services/autodream/service.py +313 -0
  365. src/harness/src/openharness/services/compact/__init__.py +1725 -0
  366. src/harness/src/openharness/services/cron.py +137 -0
  367. src/harness/src/openharness/services/cron_scheduler.py +595 -0
  368. src/harness/src/openharness/services/lsp/__init__.py +216 -0
  369. src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
  370. src/harness/src/openharness/services/oauth/__init__.py +0 -0
  371. src/harness/src/openharness/services/session_backend.py +97 -0
  372. src/harness/src/openharness/services/session_memory/__init__.py +139 -0
  373. src/harness/src/openharness/services/session_storage.py +230 -0
  374. src/harness/src/openharness/services/token_estimation.py +15 -0
  375. src/harness/src/openharness/services/tool_outputs.py +55 -0
  376. src/harness/src/openharness/skills/__init__.py +44 -0
  377. src/harness/src/openharness/skills/_frontmatter.py +97 -0
  378. src/harness/src/openharness/skills/bundled/__init__.py +75 -0
  379. src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
  380. src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
  381. src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
  382. src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
  383. src/harness/src/openharness/skills/bundled/content/review.md +33 -0
  384. src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
  385. src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
  386. src/harness/src/openharness/skills/bundled/content/test.md +38 -0
  387. src/harness/src/openharness/skills/loader.py +229 -0
  388. src/harness/src/openharness/skills/registry.py +29 -0
  389. src/harness/src/openharness/skills/types.py +24 -0
  390. src/harness/src/openharness/state/__init__.py +6 -0
  391. src/harness/src/openharness/state/app_state.py +30 -0
  392. src/harness/src/openharness/state/store.py +40 -0
  393. src/harness/src/openharness/swarm/__init__.py +70 -0
  394. src/harness/src/openharness/swarm/in_process.py +693 -0
  395. src/harness/src/openharness/swarm/lockfile.py +24 -0
  396. src/harness/src/openharness/swarm/mailbox.py +522 -0
  397. src/harness/src/openharness/swarm/permission_sync.py +1168 -0
  398. src/harness/src/openharness/swarm/registry.py +410 -0
  399. src/harness/src/openharness/swarm/spawn_utils.py +229 -0
  400. src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
  401. src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
  402. src/harness/src/openharness/swarm/types.py +398 -0
  403. src/harness/src/openharness/swarm/worktree.py +315 -0
  404. src/harness/src/openharness/tasks/__init__.py +18 -0
  405. src/harness/src/openharness/tasks/local_agent_task.py +28 -0
  406. src/harness/src/openharness/tasks/local_shell_task.py +17 -0
  407. src/harness/src/openharness/tasks/manager.py +475 -0
  408. src/harness/src/openharness/tasks/stop_task.py +11 -0
  409. src/harness/src/openharness/tasks/types.py +32 -0
  410. src/harness/src/openharness/themes/__init__.py +21 -0
  411. src/harness/src/openharness/themes/builtin.py +89 -0
  412. src/harness/src/openharness/themes/loader.py +55 -0
  413. src/harness/src/openharness/themes/schema.py +54 -0
  414. src/harness/src/openharness/tools/__init__.py +107 -0
  415. src/harness/src/openharness/tools/agent_tool.py +141 -0
  416. src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
  417. src/harness/src/openharness/tools/base.py +80 -0
  418. src/harness/src/openharness/tools/bash_tool.py +259 -0
  419. src/harness/src/openharness/tools/brief_tool.py +33 -0
  420. src/harness/src/openharness/tools/config_tool.py +54 -0
  421. src/harness/src/openharness/tools/cron_create_tool.py +105 -0
  422. src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
  423. src/harness/src/openharness/tools/cron_list_tool.py +69 -0
  424. src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
  425. src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
  426. src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
  427. src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
  428. src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
  429. src/harness/src/openharness/tools/file_edit_tool.py +95 -0
  430. src/harness/src/openharness/tools/file_read_tool.py +72 -0
  431. src/harness/src/openharness/tools/file_write_tool.py +87 -0
  432. src/harness/src/openharness/tools/glob_tool.py +175 -0
  433. src/harness/src/openharness/tools/grep_tool.py +374 -0
  434. src/harness/src/openharness/tools/image_generation_tool.py +447 -0
  435. src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
  436. src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
  437. src/harness/src/openharness/tools/lsp_tool.py +154 -0
  438. src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
  439. src/harness/src/openharness/tools/mcp_tool.py +72 -0
  440. src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
  441. src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
  442. src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
  443. src/harness/src/openharness/tools/send_message_tool.py +58 -0
  444. src/harness/src/openharness/tools/skill_tool.py +43 -0
  445. src/harness/src/openharness/tools/sleep_tool.py +32 -0
  446. src/harness/src/openharness/tools/task_create_tool.py +56 -0
  447. src/harness/src/openharness/tools/task_get_tool.py +33 -0
  448. src/harness/src/openharness/tools/task_list_tool.py +35 -0
  449. src/harness/src/openharness/tools/task_output_tool.py +35 -0
  450. src/harness/src/openharness/tools/task_stop_tool.py +30 -0
  451. src/harness/src/openharness/tools/task_update_tool.py +50 -0
  452. src/harness/src/openharness/tools/team_create_tool.py +31 -0
  453. src/harness/src/openharness/tools/team_delete_tool.py +30 -0
  454. src/harness/src/openharness/tools/todo_write_tool.py +46 -0
  455. src/harness/src/openharness/tools/tool_search_tool.py +38 -0
  456. src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
  457. src/harness/src/openharness/tools/web_search_tool.py +119 -0
  458. src/harness/src/openharness/ui/__init__.py +5 -0
  459. src/harness/src/openharness/ui/app.py +320 -0
  460. src/harness/src/openharness/ui/backend_host.py +941 -0
  461. src/harness/src/openharness/ui/coordinator_drain.py +198 -0
  462. src/harness/src/openharness/ui/input.py +32 -0
  463. src/harness/src/openharness/ui/output.py +265 -0
  464. src/harness/src/openharness/ui/permission_dialog.py +14 -0
  465. src/harness/src/openharness/ui/protocol.py +247 -0
  466. src/harness/src/openharness/ui/react_launcher.py +179 -0
  467. src/harness/src/openharness/ui/runtime.py +799 -0
  468. src/harness/src/openharness/ui/textual_app.py +495 -0
  469. src/harness/src/openharness/utils/__init__.py +0 -0
  470. src/harness/src/openharness/utils/file_lock.py +86 -0
  471. src/harness/src/openharness/utils/fs.py +98 -0
  472. src/harness/src/openharness/utils/helpers.py +77 -0
  473. src/harness/src/openharness/utils/network_guard.py +340 -0
  474. src/harness/src/openharness/utils/shell.py +147 -0
  475. src/harness/src/openharness/vim/__init__.py +5 -0
  476. src/harness/src/openharness/vim/transitions.py +8 -0
  477. src/harness/src/openharness/voice/__init__.py +7 -0
  478. src/harness/src/openharness/voice/keyterms.py +10 -0
  479. src/harness/src/openharness/voice/stream_stt.py +8 -0
  480. src/harness/src/openharness/voice/voice_mode.py +44 -0
  481. src/llm/__init__.py +29 -0
  482. src/llm/image_generation.py +402 -0
  483. src/llm/provider_registry.py +419 -0
  484. src/llm/runtime_env.py +64 -0
  485. src/llm/structured_output.py +131 -0
  486. src/llm/vision.py +198 -0
  487. src/memory/__init__.py +2 -0
  488. src/memory/api/__init__.py +1 -0
  489. src/memory/api/base_memory_system_api.py +98 -0
  490. src/memory/api/base_symbolic_memory_system_api.py +190 -0
  491. src/memory/api/base_vector_memory_system_api.py +116 -0
  492. src/memory/api/faiss_memory_system_api.py +228 -0
  493. src/memory/api/slot_process_api.py +827 -0
  494. src/memory/api/symbolic_memory_system_api.py +572 -0
  495. src/memory/memory_system/__init__.py +48 -0
  496. src/memory/memory_system/component_taxonomy.py +430 -0
  497. src/memory/memory_system/llm.py +437 -0
  498. src/memory/memory_system/models.py +168 -0
  499. src/memory/memory_system/user_prompt.py +418 -0
  500. src/memory/memory_system/utils.py +282 -0
  501. src/memory/memory_system/vectorstore.py +311 -0
  502. src/memory/memory_system/working_slot.py +99 -0
  503. src/pipeline/__init__.py +129 -0
  504. src/pipeline/contracts.py +531 -0
  505. src/pipeline/discipline_taxonomy.py +916 -0
  506. src/pipeline/evidence_coverage_ledger.py +1150 -0
  507. src/pipeline/evidence_refinement.py +620 -0
  508. src/pipeline/experiment_to_symbolic.py +137 -0
  509. src/pipeline/multimodal_evidence/__init__.py +28 -0
  510. src/pipeline/multimodal_evidence/capabilities.py +107 -0
  511. src/pipeline/multimodal_evidence/claims.py +135 -0
  512. src/pipeline/multimodal_evidence/contract.py +291 -0
  513. src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
  514. src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
  515. src/pipeline/multimodal_evidence/manifest.py +254 -0
  516. src/pipeline/multimodal_evidence/native_analysis.py +479 -0
  517. src/pipeline/multimodal_evidence/perception.py +277 -0
  518. src/pipeline/multimodal_evidence/query_binding.py +65 -0
  519. src/pipeline/multimodal_evidence/rendering.py +120 -0
  520. src/pipeline/multimodal_evidence/safety.py +42 -0
  521. src/pipeline/multimodal_evidence/sampling.py +69 -0
  522. src/pipeline/multimodal_evidence/service.py +148 -0
  523. src/pipeline/multimodal_evidence/survey_integration.py +460 -0
  524. src/pipeline/paper_identity.py +52 -0
  525. src/pipeline/research_design_inventory.py +338 -0
  526. src/pipeline/research_domain_resolution.py +669 -0
  527. src/pipeline/research_identity.py +548 -0
  528. src/pipeline/research_question_contract.py +646 -0
  529. src/pipeline/retrieval_lanes.py +889 -0
  530. src/pipeline/run_loop.py +862 -0
  531. src/pipeline/science_events.py +30 -0
  532. src/pipeline/science_manifests.py +602 -0
  533. src/pipeline/science_run.py +918 -0
  534. src/pipeline/science_stages.py +709 -0
  535. src/pipeline/science_workflow.py +807 -0
  536. src/pipeline/sh_cluster_projection.py +306 -0
  537. src/pipeline/sh_graph_provenance.py +648 -0
  538. src/pipeline/subhypothesis_decomposition.py +392 -0
  539. src/pipeline/subhypothesis_evidence.py +856 -0
  540. src/pipeline/survey_evidence_plan.py +858 -0
  541. src/pipeline/survey_gap_adjudication.py +647 -0
  542. src/pipeline/survey_gap_candidates.py +840 -0
  543. src/pipeline/survey_gap_ledger.py +683 -0
  544. src/pipeline/survey_gap_triage.py +401 -0
  545. src/pipeline/survey_handoff_persistence.py +481 -0
  546. src/pipeline/survey_handoff_projection.py +457 -0
  547. src/pipeline/survey_idea_handoff.py +1112 -0
  548. src/pipeline/survey_idea_loader.py +289 -0
  549. 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,11 @@
1
+ """Default keybinding map."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ DEFAULT_KEYBINDINGS: dict[str, str] = {
7
+ "ctrl+l": "clear",
8
+ "ctrl+k": "toggle_vim",
9
+ "ctrl+v": "toggle_voice",
10
+ "ctrl+t": "tasks",
11
+ }
@@ -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)