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,153 @@
1
+ """Usage index for recalled memory entries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import timedelta
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from openharness.memory.paths import get_project_memory_dir
11
+ from openharness.memory.scan import scan_memory_files
12
+ from openharness.memory.schema import format_datetime, parse_datetime, utc_now
13
+ from openharness.memory.types import MemoryHeader
14
+ from openharness.utils.file_lock import exclusive_file_lock
15
+ from openharness.utils.fs import atomic_write_text
16
+
17
+ USAGE_INDEX_NAME = "usage_index.json"
18
+ STALE_UNUSED_DAYS = 60
19
+ STALE_MAX_IMPORTANCE = 1
20
+
21
+
22
+ def get_usage_index_path(cwd: str | Path, *, memory_dir: str | Path | None = None) -> Path:
23
+ """Return the usage index path for a memory store."""
24
+
25
+ root = Path(memory_dir) if memory_dir is not None else get_project_memory_dir(cwd)
26
+ root.mkdir(parents=True, exist_ok=True)
27
+ return root / USAGE_INDEX_NAME
28
+
29
+
30
+ def load_usage_index(cwd: str | Path, *, memory_dir: str | Path | None = None) -> dict[str, Any]:
31
+ """Load usage index data, returning an empty index for invalid files."""
32
+
33
+ path = get_usage_index_path(cwd, memory_dir=memory_dir)
34
+ if not path.exists():
35
+ return _empty_index()
36
+ try:
37
+ data = json.loads(path.read_text(encoding="utf-8"))
38
+ except (OSError, json.JSONDecodeError):
39
+ return _empty_index()
40
+ if not isinstance(data, dict):
41
+ return _empty_index()
42
+ memories = data.get("memories")
43
+ if not isinstance(memories, dict):
44
+ memories = {}
45
+ normalized = _empty_index()
46
+ normalized["memories"] = {
47
+ str(memory_id): _normalize_usage_record(record)
48
+ for memory_id, record in memories.items()
49
+ if isinstance(record, dict)
50
+ }
51
+ return normalized
52
+
53
+
54
+ def save_usage_index(
55
+ cwd: str | Path,
56
+ index: dict[str, Any],
57
+ *,
58
+ memory_dir: str | Path | None = None,
59
+ ) -> None:
60
+ """Persist usage index data atomically."""
61
+
62
+ path = get_usage_index_path(cwd, memory_dir=memory_dir)
63
+ payload = json.dumps(index, indent=2, ensure_ascii=False, sort_keys=True) + "\n"
64
+ atomic_write_text(path, payload)
65
+
66
+
67
+ def get_memory_usage(
68
+ cwd: str | Path,
69
+ memory_id: str,
70
+ *,
71
+ memory_dir: str | Path | None = None,
72
+ ) -> dict[str, Any]:
73
+ """Return usage data for a memory id."""
74
+
75
+ if not memory_id:
76
+ return _normalize_usage_record({})
77
+ index = load_usage_index(cwd, memory_dir=memory_dir)
78
+ record = index["memories"].get(memory_id, {})
79
+ return _normalize_usage_record(record)
80
+
81
+
82
+ def mark_memory_used(
83
+ cwd: str | Path,
84
+ memories: list[MemoryHeader],
85
+ *,
86
+ memory_dir: str | Path | None = None,
87
+ ) -> None:
88
+ """Record that memory entries were recalled into a runtime prompt."""
89
+
90
+ usable = [header for header in memories if header.id]
91
+ if not usable:
92
+ return
93
+ resolved_memory_dir = Path(memory_dir) if memory_dir is not None else usable[0].path.parent
94
+ lock_path = resolved_memory_dir / ".usage_index.lock"
95
+ with exclusive_file_lock(lock_path):
96
+ index = load_usage_index(cwd, memory_dir=resolved_memory_dir)
97
+ now = format_datetime(utc_now())
98
+ for header in usable:
99
+ record = _normalize_usage_record(index["memories"].get(header.id, {}))
100
+ record["use_count"] = int(record["use_count"]) + 1
101
+ record["last_used_at"] = now
102
+ record["path"] = header.path.name
103
+ index["memories"][header.id] = record
104
+ save_usage_index(cwd, index, memory_dir=resolved_memory_dir)
105
+
106
+
107
+ def find_stale_memory_candidates(
108
+ cwd: str | Path,
109
+ *,
110
+ memory_dir: str | Path | None = None,
111
+ ) -> list[MemoryHeader]:
112
+ """Return low-value unused memories that auto-dream should review for pruning."""
113
+
114
+ resolved_memory_dir = Path(memory_dir) if memory_dir is not None else None
115
+ headers = scan_memory_files(
116
+ cwd,
117
+ max_files=None,
118
+ include_disabled=False,
119
+ include_expired=False,
120
+ memory_dir=resolved_memory_dir,
121
+ )
122
+ now = utc_now()
123
+ candidates: list[MemoryHeader] = []
124
+ for header in headers:
125
+ if header.importance > STALE_MAX_IMPORTANCE:
126
+ continue
127
+ usage = get_memory_usage(cwd, header.id, memory_dir=resolved_memory_dir or header.path.parent)
128
+ if int(usage["use_count"]) > 0:
129
+ continue
130
+ updated_at = parse_datetime(header.updated_at) or parse_datetime(header.created_at)
131
+ if updated_at is None:
132
+ continue
133
+ if now - updated_at >= timedelta(days=STALE_UNUSED_DAYS):
134
+ candidates.append(header)
135
+ candidates.sort(key=lambda item: (item.importance, item.updated_at or "", item.path.name))
136
+ return candidates
137
+
138
+
139
+ def _empty_index() -> dict[str, Any]:
140
+ return {"version": 1, "memories": {}}
141
+
142
+
143
+ def _normalize_usage_record(record: dict[str, Any]) -> dict[str, Any]:
144
+ use_count = record.get("use_count", 0)
145
+ try:
146
+ use_count = max(0, int(use_count))
147
+ except (TypeError, ValueError):
148
+ use_count = 0
149
+ return {
150
+ "last_used_at": str(record.get("last_used_at") or ""),
151
+ "use_count": use_count,
152
+ "path": str(record.get("path") or ""),
153
+ }
@@ -0,0 +1,5 @@
1
+ """Output styles exports."""
2
+
3
+ from openharness.output_styles.loader import OutputStyle, get_output_styles_dir, load_output_styles
4
+
5
+ __all__ = ["OutputStyle", "get_output_styles_dir", "load_output_styles"]
@@ -0,0 +1,42 @@
1
+ """Output style loading."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from openharness.config.paths import get_config_dir
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class OutputStyle:
13
+ """A named output style."""
14
+
15
+ name: str
16
+ content: str
17
+ source: str
18
+
19
+
20
+ def get_output_styles_dir() -> Path:
21
+ """Return the custom output styles directory."""
22
+ path = get_config_dir() / "output_styles"
23
+ path.mkdir(parents=True, exist_ok=True)
24
+ return path
25
+
26
+
27
+ def load_output_styles() -> list[OutputStyle]:
28
+ """Load bundled and custom output styles."""
29
+ styles = [
30
+ OutputStyle(name="default", content="Standard rich console output.", source="builtin"),
31
+ OutputStyle(name="minimal", content="Very terse plain-text output.", source="builtin"),
32
+ OutputStyle(name="codex", content="Codex-like compact transcript and tool output.", source="builtin"),
33
+ ]
34
+ for path in sorted(get_output_styles_dir().glob("*.md")):
35
+ styles.append(
36
+ OutputStyle(
37
+ name=path.stem,
38
+ content=path.read_text(encoding="utf-8"),
39
+ source="user",
40
+ )
41
+ )
42
+ return styles
@@ -0,0 +1,26 @@
1
+ """Permission helpers for OpenHarness."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING: # pragma: no cover
8
+ from openharness.permissions.checker import PermissionChecker, PermissionDecision
9
+ from openharness.permissions.modes import PermissionMode
10
+
11
+ __all__ = ["PermissionChecker", "PermissionDecision", "PermissionMode"]
12
+
13
+
14
+ def __getattr__(name: str):
15
+ if name in {"PermissionChecker", "PermissionDecision"}:
16
+ from openharness.permissions.checker import PermissionChecker, PermissionDecision
17
+
18
+ return {
19
+ "PermissionChecker": PermissionChecker,
20
+ "PermissionDecision": PermissionDecision,
21
+ }[name]
22
+ if name == "PermissionMode":
23
+ from openharness.permissions.modes import PermissionMode
24
+
25
+ return PermissionMode
26
+ raise AttributeError(name)
@@ -0,0 +1,200 @@
1
+ """Permission checking for tool execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import fnmatch
6
+ import logging
7
+ from dataclasses import dataclass
8
+
9
+ from openharness.config.settings import PermissionSettings
10
+ from openharness.permissions.modes import PermissionMode
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+ # Paths that are always denied regardless of permission mode or user config.
15
+ # These protect high-value credential and key material from LLM-directed access
16
+ # (including via prompt injection). Patterns use fnmatch syntax and are matched
17
+ # against the fully-resolved absolute path produced by the query engine.
18
+ SENSITIVE_PATH_PATTERNS: tuple[str, ...] = (
19
+ # SSH keys and config
20
+ "*/.ssh/*",
21
+ # AWS credentials
22
+ "*/.aws/credentials",
23
+ "*/.aws/config",
24
+ # GCP credentials
25
+ "*/.config/gcloud/*",
26
+ # Azure credentials
27
+ "*/.azure/*",
28
+ # GPG keys
29
+ "*/.gnupg/*",
30
+ # Docker credentials
31
+ "*/.docker/config.json",
32
+ # Kubernetes credentials
33
+ "*/.kube/config",
34
+ # OpenHarness own credential stores
35
+ "*/.openharness/credentials.json",
36
+ "*/.openharness/copilot_auth.json",
37
+ )
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class PermissionDecision:
42
+ """Result of checking whether a tool invocation may run."""
43
+
44
+ allowed: bool
45
+ requires_confirmation: bool = False
46
+ reason: str = ""
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class PathRule:
51
+ """A glob-based path permission rule."""
52
+
53
+ pattern: str
54
+ allow: bool # True = allow, False = deny
55
+
56
+
57
+ class PermissionChecker:
58
+ """Evaluate tool usage against the configured permission mode and rules."""
59
+
60
+ def __init__(self, settings: PermissionSettings) -> None:
61
+ self._settings = settings
62
+ # Parse path rules from settings
63
+ self._path_rules: list[PathRule] = []
64
+ for rule in getattr(settings, "path_rules", []):
65
+ pattern = getattr(rule, "pattern", None) or (rule.get("pattern") if isinstance(rule, dict) else None)
66
+ allow = getattr(rule, "allow", True) if not isinstance(rule, dict) else rule.get("allow", True)
67
+ if isinstance(pattern, str) and pattern.strip():
68
+ self._path_rules.append(PathRule(pattern=pattern.strip(), allow=allow))
69
+ else:
70
+ log.warning(
71
+ "Skipping path rule with missing, empty, or non-string 'pattern' field: %r",
72
+ rule,
73
+ )
74
+
75
+ def evaluate(
76
+ self,
77
+ tool_name: str,
78
+ *,
79
+ is_read_only: bool,
80
+ file_path: str | None = None,
81
+ command: str | None = None,
82
+ ) -> PermissionDecision:
83
+ """Return whether the tool may run immediately."""
84
+ # Built-in sensitive path protection — always active, cannot be
85
+ # overridden by user settings or permission mode. This is a
86
+ # defence-in-depth measure against LLM-directed or prompt-injection
87
+ # driven access to credential files.
88
+ if file_path:
89
+ for candidate_path in _policy_match_paths(file_path):
90
+ for pattern in SENSITIVE_PATH_PATTERNS:
91
+ if fnmatch.fnmatch(candidate_path, pattern):
92
+ return PermissionDecision(
93
+ allowed=False,
94
+ reason=(
95
+ f"Access denied: {file_path} is a sensitive credential path "
96
+ f"(matched built-in pattern '{pattern}')"
97
+ ),
98
+ )
99
+
100
+ # Explicit tool deny list
101
+ if tool_name in self._settings.denied_tools:
102
+ return PermissionDecision(allowed=False, reason=f"{tool_name} is explicitly denied")
103
+
104
+ # Explicit tool allow list
105
+ if tool_name in self._settings.allowed_tools:
106
+ return PermissionDecision(allowed=True, reason=f"{tool_name} is explicitly allowed")
107
+
108
+ # Check path-level rules
109
+ if file_path and self._path_rules:
110
+ for candidate_path in _policy_match_paths(file_path):
111
+ for rule in self._path_rules:
112
+ if fnmatch.fnmatch(candidate_path, rule.pattern):
113
+ if not rule.allow:
114
+ return PermissionDecision(
115
+ allowed=False,
116
+ reason=f"Path {file_path} matches deny rule: {rule.pattern}",
117
+ )
118
+
119
+ # Check command deny patterns (e.g. deny "rm -rf /")
120
+ if command:
121
+ for pattern in getattr(self._settings, "denied_commands", []):
122
+ if isinstance(pattern, str) and fnmatch.fnmatch(command, pattern):
123
+ return PermissionDecision(
124
+ allowed=False,
125
+ reason=f"Command matches deny pattern: {pattern}",
126
+ )
127
+
128
+ # Full auto: allow everything
129
+ if self._settings.mode == PermissionMode.FULL_AUTO:
130
+ return PermissionDecision(allowed=True, reason="Auto mode allows all tools")
131
+
132
+ # Read-only tools always allowed
133
+ if is_read_only:
134
+ return PermissionDecision(allowed=True, reason="read-only tools are allowed")
135
+
136
+ # Plan mode: block mutating tools
137
+ if self._settings.mode == PermissionMode.PLAN:
138
+ return PermissionDecision(
139
+ allowed=False,
140
+ reason="Plan mode blocks mutating tools until the user exits plan mode",
141
+ )
142
+
143
+ # Default mode: require confirmation for mutating tools
144
+ bash_hint = _bash_permission_hint(command)
145
+ reason = (
146
+ "Mutating tools require user confirmation in default mode. "
147
+ "Approve the prompt when asked, or run /permissions full_auto "
148
+ "if you want to allow them for this session."
149
+ )
150
+ if bash_hint:
151
+ reason = f"{reason} {bash_hint}"
152
+ return PermissionDecision(
153
+ allowed=False,
154
+ requires_confirmation=True,
155
+ reason=reason,
156
+ )
157
+
158
+
159
+ def _policy_match_paths(file_path: str) -> tuple[str, ...]:
160
+ """Return path forms that should participate in policy matching.
161
+
162
+ Directory-scoped tools like ``grep`` and ``glob`` may operate on a root such
163
+ as ``/home/user/.ssh``. Appending a trailing slash lets glob-style deny
164
+ patterns like ``*/.ssh/*`` and ``/etc/*`` match the directory root itself.
165
+ """
166
+ normalized = file_path.rstrip("/")
167
+ if not normalized:
168
+ return (file_path,)
169
+ return (normalized, normalized + "/")
170
+
171
+
172
+ def _bash_permission_hint(command: str | None) -> str:
173
+ if not command:
174
+ return ""
175
+ lowered = command.lower()
176
+ install_markers = (
177
+ "npm install",
178
+ "pnpm install",
179
+ "yarn install",
180
+ "bun install",
181
+ "pip install",
182
+ "uv pip install",
183
+ "poetry install",
184
+ "cargo install",
185
+ "create-next-app",
186
+ "npm create ",
187
+ "pnpm create ",
188
+ "yarn create ",
189
+ "bun create ",
190
+ "npx create-",
191
+ "npm init ",
192
+ "pnpm init ",
193
+ "yarn init ",
194
+ )
195
+ if any(marker in lowered for marker in install_markers):
196
+ return (
197
+ "Package installation and scaffolding commands change the workspace, "
198
+ "so they will not run automatically in default mode."
199
+ )
200
+ return ""
@@ -0,0 +1,13 @@
1
+ """Permission mode definitions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class PermissionMode(str, Enum):
9
+ """Supported permission modes."""
10
+
11
+ DEFAULT = "default"
12
+ PLAN = "plan"
13
+ FULL_AUTO = "full_auto"
@@ -0,0 +1,6 @@
1
+ """Session personalization — auto-extract local rules from conversation history."""
2
+
3
+ from openharness.personalization.extractor import extract_local_rules
4
+ from openharness.personalization.rules import load_local_rules, save_local_rules
5
+
6
+ __all__ = ["extract_local_rules", "load_local_rules", "save_local_rules"]
@@ -0,0 +1,138 @@
1
+ """Extract local rules from session conversation history."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+ # Patterns that indicate environment-specific facts worth capturing
11
+ _FACT_PATTERNS: list[tuple[str, str, re.Pattern]] = [
12
+ ("ssh_host", "SSH connection", re.compile(
13
+ r"ssh\s+(?:-[io]\s+\S+\s+)*(\S+@[\d.]+|\S+@\S+)", re.IGNORECASE
14
+ )),
15
+ ("ip_address", "Server IP", re.compile(
16
+ r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
17
+ )),
18
+ ("data_path", "Data path", re.compile(
19
+ r"(/(?:ext|mnt|home|data|root)\S*/(?:data\S*|landing|derived|reference)\S*)"
20
+ )),
21
+ ("conda_env", "Conda environment", re.compile(
22
+ r"conda\s+activate\s+(\S+)"
23
+ )),
24
+ ("python_env", "Python version", re.compile(
25
+ r"[Pp]ython\s*(3\.\d+(?:\.\d+)?)"
26
+ )),
27
+ ("api_endpoint", "API endpoint", re.compile(
28
+ r"(https?://\S+/v\d+/?)\b"
29
+ )),
30
+ ("env_var", "Environment variable", re.compile(
31
+ r"export\s+([A-Z][A-Z0-9_]+)(?:=\S+)?"
32
+ )),
33
+ ("git_remote", "Git remote", re.compile(
34
+ r"(?:github|gitlab)\.com[:/](\S+?)(?:\.git)?"
35
+ )),
36
+ ("ray_cluster", "Ray cluster", re.compile(
37
+ r"ray\s+(?:start|init|submit)\b.*?(--address\s+\S+|\d+\.\d+\.\d+\.\d+:\d+)",
38
+ re.IGNORECASE,
39
+ )),
40
+ ("cron_schedule", "Cron schedule", re.compile(
41
+ r"((?:\d+|\*)\s+(?:\d+|\*)\s+(?:\d+|\*)\s+(?:\d+|\*)\s+(?:\d+|\*))\s+\S+"
42
+ )),
43
+ ]
44
+
45
+
46
+ def extract_facts_from_text(text: str) -> list[dict]:
47
+ """Extract environment-specific facts from conversation text using patterns."""
48
+ facts = []
49
+ seen_keys = set()
50
+
51
+ for fact_type, label, pattern in _FACT_PATTERNS:
52
+ for match in pattern.finditer(text):
53
+ value = match.group(1) if match.lastindex else match.group(0)
54
+ value = value.strip().rstrip(".,;:)")
55
+ if not value or len(value) < 3:
56
+ continue
57
+
58
+ # Skip common false positives
59
+ if fact_type == "ip_address" and value.startswith(("0.", "255.", "127.0.0.1")):
60
+ continue
61
+
62
+ key = f"{fact_type}:{value}"
63
+ if key in seen_keys:
64
+ continue
65
+ seen_keys.add(key)
66
+
67
+ facts.append({
68
+ "key": key,
69
+ "type": fact_type,
70
+ "label": label,
71
+ "value": value,
72
+ "confidence": 0.7,
73
+ })
74
+
75
+ return facts
76
+
77
+
78
+ def extract_local_rules(session_messages: list[dict]) -> list[dict]:
79
+ """Extract environment facts from a list of session messages.
80
+
81
+ Args:
82
+ session_messages: List of message dicts with 'role' and 'content' keys.
83
+
84
+ Returns:
85
+ List of fact dicts with key, type, label, value, confidence.
86
+ """
87
+ all_text = []
88
+ for msg in session_messages:
89
+ content = msg.get("content", "")
90
+ if isinstance(content, str):
91
+ all_text.append(content)
92
+ elif isinstance(content, list):
93
+ for block in content:
94
+ if isinstance(block, dict) and "text" in block:
95
+ all_text.append(block["text"])
96
+
97
+ combined = "\n".join(all_text)
98
+ return extract_facts_from_text(combined)
99
+
100
+
101
+ def facts_to_rules_markdown(facts: list[dict]) -> str:
102
+ """Convert extracted facts to a markdown rules document."""
103
+ if not facts:
104
+ return ""
105
+
106
+ grouped: dict[str, list[dict]] = {}
107
+ for f in facts:
108
+ grouped.setdefault(f["type"], []).append(f)
109
+
110
+ lines = [
111
+ "# Local Environment Rules",
112
+ "",
113
+ "*Auto-generated from session history. Do not edit manually.*",
114
+ "",
115
+ ]
116
+
117
+ section_titles = {
118
+ "ssh_host": "SSH Hosts",
119
+ "ip_address": "Known Servers",
120
+ "data_path": "Data Paths",
121
+ "conda_env": "Python Environments",
122
+ "python_env": "Python Versions",
123
+ "api_endpoint": "API Endpoints",
124
+ "env_var": "Environment Variables",
125
+ "git_remote": "Git Repositories",
126
+ "ray_cluster": "Ray Cluster Config",
127
+ "cron_schedule": "Scheduled Jobs",
128
+ }
129
+
130
+ for fact_type, items in grouped.items():
131
+ title = section_titles.get(fact_type, fact_type.replace("_", " ").title())
132
+ lines.append(f"## {title}")
133
+ lines.append("")
134
+ for item in items:
135
+ lines.append(f"- `{item['value']}`")
136
+ lines.append("")
137
+
138
+ return "\n".join(lines)
@@ -0,0 +1,64 @@
1
+ """Local rules file management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+
9
+ _RULES_DIR = Path("~/.openharness/local_rules").expanduser()
10
+ _RULES_FILE = _RULES_DIR / "rules.md"
11
+ _FACTS_FILE = _RULES_DIR / "facts.json"
12
+
13
+
14
+ def _ensure_dir() -> None:
15
+ _RULES_DIR.mkdir(parents=True, exist_ok=True)
16
+
17
+
18
+ def load_local_rules() -> str:
19
+ """Load the local rules markdown, or empty string if none exist."""
20
+ if _RULES_FILE.exists():
21
+ return _RULES_FILE.read_text(encoding="utf-8").strip()
22
+ return ""
23
+
24
+
25
+ def save_local_rules(content: str) -> Path:
26
+ """Write local rules markdown."""
27
+ _ensure_dir()
28
+ _RULES_FILE.write_text(content.strip() + "\n", encoding="utf-8")
29
+ return _RULES_FILE
30
+
31
+
32
+ def load_facts() -> dict:
33
+ """Load extracted facts as a dict."""
34
+ if _FACTS_FILE.exists():
35
+ return json.loads(_FACTS_FILE.read_text(encoding="utf-8"))
36
+ return {"facts": [], "last_updated": None}
37
+
38
+
39
+ def save_facts(facts: dict) -> None:
40
+ """Persist extracted facts."""
41
+ _ensure_dir()
42
+ facts["last_updated"] = datetime.now(timezone.utc).isoformat()
43
+ _FACTS_FILE.write_text(
44
+ json.dumps(facts, indent=2, ensure_ascii=False) + "\n",
45
+ encoding="utf-8",
46
+ )
47
+
48
+
49
+ def merge_facts(existing: dict, new_facts: list[dict]) -> dict:
50
+ """Merge new facts into existing, deduplicating by key."""
51
+ by_key = {}
52
+ for f in existing.get("facts", []):
53
+ by_key[f["key"]] = f
54
+ for f in new_facts:
55
+ key = f.get("key", "")
56
+ if key:
57
+ if key in by_key:
58
+ # Update with newer value, keep higher confidence
59
+ old = by_key[key]
60
+ if f.get("confidence", 0) >= old.get("confidence", 0):
61
+ by_key[key] = f
62
+ else:
63
+ by_key[key] = f
64
+ return {"facts": list(by_key.values())}