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,148 @@
1
+ """Adapter around the ``srt`` sandbox-runtime CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import shlex
7
+ import shutil
8
+ import tempfile
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from openharness.config import Settings, load_settings
14
+ from openharness.platforms import get_platform, get_platform_capabilities
15
+
16
+
17
+ class SandboxUnavailableError(RuntimeError):
18
+ """Raised when sandboxing is required but unavailable."""
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class SandboxAvailability:
23
+ """Computed sandbox-runtime availability for the current environment."""
24
+
25
+ enabled: bool
26
+ available: bool
27
+ reason: str | None = None
28
+ command: str | None = None
29
+
30
+ @property
31
+ def active(self) -> bool:
32
+ """Return whether sandboxing should be applied to child processes."""
33
+ return self.enabled and self.available
34
+
35
+
36
+ def build_sandbox_runtime_config(settings: Settings) -> dict[str, Any]:
37
+ """Convert OpenHarness settings into an ``srt`` settings payload."""
38
+ return {
39
+ "network": {
40
+ "allowedDomains": list(settings.sandbox.network.allowed_domains),
41
+ "deniedDomains": list(settings.sandbox.network.denied_domains),
42
+ },
43
+ "filesystem": {
44
+ "allowRead": list(settings.sandbox.filesystem.allow_read),
45
+ "denyRead": list(settings.sandbox.filesystem.deny_read),
46
+ "allowWrite": list(settings.sandbox.filesystem.allow_write),
47
+ "denyWrite": list(settings.sandbox.filesystem.deny_write),
48
+ },
49
+ }
50
+
51
+
52
+ def get_sandbox_availability(settings: Settings | None = None) -> SandboxAvailability:
53
+ """Return whether ``srt`` can be used for the current runtime."""
54
+ resolved_settings = settings or load_settings()
55
+ if not resolved_settings.sandbox.enabled:
56
+ return SandboxAvailability(enabled=False, available=False, reason="sandbox is disabled")
57
+
58
+ platform_name = get_platform()
59
+ capabilities = get_platform_capabilities(platform_name)
60
+ if not capabilities.supports_sandbox_runtime:
61
+ if platform_name == "windows":
62
+ reason = "sandbox runtime is not supported on native Windows; use WSL for sandboxed execution"
63
+ else:
64
+ reason = f"sandbox runtime is not supported on platform {platform_name}"
65
+ return SandboxAvailability(enabled=True, available=False, reason=reason)
66
+
67
+ enabled_platforms = {name.lower() for name in resolved_settings.sandbox.enabled_platforms}
68
+ if enabled_platforms and platform_name not in enabled_platforms:
69
+ return SandboxAvailability(
70
+ enabled=True,
71
+ available=False,
72
+ reason=f"sandbox is disabled for platform {platform_name} by configuration",
73
+ )
74
+
75
+ srt = shutil.which("srt")
76
+ if not srt:
77
+ return SandboxAvailability(
78
+ enabled=True,
79
+ available=False,
80
+ reason=(
81
+ "sandbox runtime CLI not found; install it with "
82
+ "`npm install -g @anthropic-ai/sandbox-runtime`"
83
+ ),
84
+ )
85
+
86
+ if platform_name in {"linux", "wsl"} and shutil.which("bwrap") is None:
87
+ return SandboxAvailability(
88
+ enabled=True,
89
+ available=False,
90
+ reason="bubblewrap (`bwrap`) is required for sandbox runtime on Linux/WSL",
91
+ command=srt,
92
+ )
93
+
94
+ if platform_name == "macos" and shutil.which("sandbox-exec") is None:
95
+ return SandboxAvailability(
96
+ enabled=True,
97
+ available=False,
98
+ reason="`sandbox-exec` is required for sandbox runtime on macOS",
99
+ command=srt,
100
+ )
101
+
102
+ return SandboxAvailability(enabled=True, available=True, command=srt)
103
+
104
+
105
+ def wrap_command_for_sandbox(
106
+ command: list[str],
107
+ *,
108
+ settings: Settings | None = None,
109
+ ) -> tuple[list[str], Path | None]:
110
+ """Wrap an argv list with ``srt`` when sandboxing is active."""
111
+ resolved_settings = settings or load_settings()
112
+ if resolved_settings.sandbox.backend == "docker":
113
+ return command, None
114
+ availability = get_sandbox_availability(resolved_settings)
115
+ if not availability.active:
116
+ if resolved_settings.sandbox.enabled and resolved_settings.sandbox.fail_if_unavailable:
117
+ raise SandboxUnavailableError(availability.reason or "sandbox runtime is unavailable")
118
+ return command, None
119
+
120
+ settings_path = _write_runtime_settings(build_sandbox_runtime_config(resolved_settings))
121
+ # The ``srt`` argv form does not reliably preserve child exit codes for shell-style
122
+ # commands such as ``bash -lc 'exit 1'``. Build a single escaped command string and
123
+ # pass it through ``-c`` so hook/tool failures still propagate correctly.
124
+ wrapped = [
125
+ availability.command or "srt",
126
+ "--settings",
127
+ str(settings_path),
128
+ "-c",
129
+ shlex.join(command),
130
+ ]
131
+ return wrapped, settings_path
132
+
133
+
134
+ def _write_runtime_settings(payload: dict[str, Any]) -> Path:
135
+ """Persist a temporary settings file for one sandboxed child process."""
136
+ tmp = tempfile.NamedTemporaryFile(
137
+ mode="w",
138
+ encoding="utf-8",
139
+ prefix="openharness-sandbox-",
140
+ suffix=".json",
141
+ delete=False,
142
+ )
143
+ try:
144
+ json.dump(payload, tmp)
145
+ tmp.write("\n")
146
+ finally:
147
+ tmp.close()
148
+ return Path(tmp.name)
@@ -0,0 +1,232 @@
1
+ """Docker-based sandbox backend for isolated tool execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import shutil
8
+ import subprocess
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+
12
+ from openharness.config import Settings
13
+ from openharness.platforms import get_platform, get_platform_capabilities
14
+ from openharness.sandbox.adapter import SandboxAvailability, SandboxUnavailableError
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def get_docker_availability(settings: Settings) -> SandboxAvailability:
20
+ """Check whether Docker can be used as a sandbox backend."""
21
+ if not settings.sandbox.enabled or settings.sandbox.backend != "docker":
22
+ return SandboxAvailability(
23
+ enabled=False, available=False, reason="Docker sandbox is not enabled"
24
+ )
25
+
26
+ platform_name = get_platform()
27
+ capabilities = get_platform_capabilities(platform_name)
28
+ if not capabilities.supports_docker_sandbox:
29
+ return SandboxAvailability(
30
+ enabled=True,
31
+ available=False,
32
+ reason=f"Docker sandbox is not supported on platform {platform_name}",
33
+ )
34
+
35
+ docker = shutil.which("docker")
36
+ if not docker:
37
+ return SandboxAvailability(
38
+ enabled=True,
39
+ available=False,
40
+ reason="Docker CLI not found; install Docker Desktop or Docker Engine",
41
+ )
42
+
43
+ try:
44
+ subprocess.run(
45
+ [docker, "info"],
46
+ capture_output=True,
47
+ timeout=5,
48
+ check=True,
49
+ )
50
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError):
51
+ return SandboxAvailability(
52
+ enabled=True,
53
+ available=False,
54
+ reason="Docker daemon is not running",
55
+ command=docker,
56
+ )
57
+
58
+ return SandboxAvailability(enabled=True, available=True, command=docker)
59
+
60
+
61
+ @dataclass
62
+ class DockerSandboxSession:
63
+ """Manages a long-running Docker container for one OpenHarness session."""
64
+
65
+ settings: Settings
66
+ session_id: str
67
+ cwd: Path
68
+ _container_name: str = field(init=False)
69
+ _running: bool = field(init=False, default=False)
70
+
71
+ def __post_init__(self) -> None:
72
+ self._container_name = f"openharness-sandbox-{self.session_id}"
73
+
74
+ @property
75
+ def container_name(self) -> str:
76
+ return self._container_name
77
+
78
+ @property
79
+ def is_running(self) -> bool:
80
+ return self._running
81
+
82
+ def _build_run_argv(self) -> list[str]:
83
+ """Build the ``docker run`` argv for container creation."""
84
+ docker = shutil.which("docker") or "docker"
85
+ sandbox = self.settings.sandbox
86
+ docker_cfg = sandbox.docker
87
+ cwd_str = str(self.cwd.resolve())
88
+
89
+ argv = [
90
+ docker,
91
+ "run",
92
+ "-d",
93
+ "--rm",
94
+ "--name",
95
+ self._container_name,
96
+ ]
97
+
98
+ # Docker backend currently supports only fully disabled networking.
99
+ # Domain-level allow/deny policies exist for the srt backend, but Docker
100
+ # does not enforce them yet. Fail closed instead of silently widening
101
+ # egress to unrestricted bridge networking.
102
+ if sandbox.network.allowed_domains or sandbox.network.denied_domains:
103
+ logger.warning(
104
+ "Docker sandbox does not enforce allowed_domains/denied_domains yet; "
105
+ "keeping network disabled"
106
+ )
107
+ argv.extend(["--network", "none"])
108
+
109
+ # Resource limits
110
+ if docker_cfg.cpu_limit > 0:
111
+ argv.extend(["--cpus", str(docker_cfg.cpu_limit)])
112
+ if docker_cfg.memory_limit:
113
+ argv.extend(["--memory", docker_cfg.memory_limit])
114
+
115
+ # Bind-mount project directory at the same path
116
+ argv.extend(["-v", f"{cwd_str}:{cwd_str}"])
117
+ argv.extend(["-w", cwd_str])
118
+
119
+ # Extra mounts
120
+ for mount in docker_cfg.extra_mounts:
121
+ argv.extend(["-v", mount])
122
+
123
+ # Extra environment variables
124
+ for key, value in docker_cfg.extra_env.items():
125
+ argv.extend(["-e", f"{key}={value}"])
126
+
127
+ argv.extend([docker_cfg.image, "tail", "-f", "/dev/null"])
128
+ return argv
129
+
130
+ async def start(self) -> None:
131
+ """Create and start the sandbox container."""
132
+ from openharness.sandbox.docker_image import ensure_image_available
133
+
134
+ docker_cfg = self.settings.sandbox.docker
135
+ available = await ensure_image_available(
136
+ docker_cfg.image, docker_cfg.auto_build_image
137
+ )
138
+ if not available:
139
+ raise SandboxUnavailableError(
140
+ f"Docker image {docker_cfg.image!r} is not available and "
141
+ "auto_build_image is disabled"
142
+ )
143
+
144
+ argv = self._build_run_argv()
145
+ logger.info("Starting Docker sandbox: %s", " ".join(argv))
146
+
147
+ process = await asyncio.create_subprocess_exec(
148
+ *argv,
149
+ stdout=asyncio.subprocess.PIPE,
150
+ stderr=asyncio.subprocess.PIPE,
151
+ )
152
+ stdout, stderr = await process.communicate()
153
+ if process.returncode != 0:
154
+ msg = stderr.decode("utf-8", errors="replace").strip()
155
+ raise SandboxUnavailableError(f"Failed to start Docker sandbox: {msg}")
156
+
157
+ self._running = True
158
+ logger.info("Docker sandbox started: %s", self._container_name)
159
+
160
+ async def stop(self) -> None:
161
+ """Stop and remove the sandbox container."""
162
+ if not self._running:
163
+ return
164
+ docker = shutil.which("docker") or "docker"
165
+ try:
166
+ process = await asyncio.create_subprocess_exec(
167
+ docker,
168
+ "stop",
169
+ "-t",
170
+ "5",
171
+ self._container_name,
172
+ stdout=asyncio.subprocess.PIPE,
173
+ stderr=asyncio.subprocess.PIPE,
174
+ )
175
+ await asyncio.wait_for(process.communicate(), timeout=15)
176
+ except (asyncio.TimeoutError, OSError) as exc:
177
+ logger.warning("Error stopping Docker sandbox: %s", exc)
178
+ finally:
179
+ self._running = False
180
+ logger.info("Docker sandbox stopped: %s", self._container_name)
181
+
182
+ def stop_sync(self) -> None:
183
+ """Synchronous stop for use in atexit handlers."""
184
+ if not self._running:
185
+ return
186
+ docker = shutil.which("docker") or "docker"
187
+ try:
188
+ subprocess.run(
189
+ [docker, "stop", "-t", "3", self._container_name],
190
+ capture_output=True,
191
+ timeout=10,
192
+ )
193
+ except (subprocess.TimeoutExpired, OSError):
194
+ pass
195
+ finally:
196
+ self._running = False
197
+
198
+ async def exec_command(
199
+ self,
200
+ argv: list[str],
201
+ *,
202
+ cwd: str | Path,
203
+ stdin: int | None = None,
204
+ stdout: int | None = None,
205
+ stderr: int | None = None,
206
+ env: dict[str, str] | None = None,
207
+ ) -> asyncio.subprocess.Process:
208
+ """Execute a command inside the sandbox container.
209
+
210
+ Returns an ``asyncio.subprocess.Process`` with the same interface as
211
+ ``asyncio.create_subprocess_exec``.
212
+ """
213
+ if not self._running:
214
+ raise SandboxUnavailableError("Docker sandbox session is not running")
215
+
216
+ docker = shutil.which("docker") or "docker"
217
+ cmd: list[str] = [docker, "exec"]
218
+ cmd.extend(["-w", str(Path(cwd).resolve())])
219
+
220
+ if env:
221
+ for key, value in env.items():
222
+ cmd.extend(["-e", f"{key}={value}"])
223
+
224
+ cmd.append(self._container_name)
225
+ cmd.extend(argv)
226
+
227
+ return await asyncio.create_subprocess_exec(
228
+ *cmd,
229
+ stdin=stdin,
230
+ stdout=stdout,
231
+ stderr=stderr,
232
+ )
@@ -0,0 +1,103 @@
1
+ """Docker image availability and build helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import shutil
8
+ from pathlib import Path
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ _DEFAULT_IMAGE = "openharness-sandbox:latest"
13
+
14
+ _DOCKERFILE_CONTENT = """\
15
+ FROM python:3.11-slim
16
+ RUN apt-get update && apt-get install -y --no-install-recommends \\
17
+ ripgrep bash git && \\
18
+ rm -rf /var/lib/apt/lists/*
19
+ RUN useradd -m -s /bin/bash ohuser
20
+ USER ohuser
21
+ """
22
+
23
+
24
+ def get_dockerfile_content() -> str:
25
+ """Return the default Dockerfile content for the sandbox image."""
26
+ return _DOCKERFILE_CONTENT
27
+
28
+
29
+ async def _image_exists(image: str) -> bool:
30
+ """Check whether a Docker image exists locally."""
31
+ docker = shutil.which("docker") or "docker"
32
+ process = await asyncio.create_subprocess_exec(
33
+ docker,
34
+ "image",
35
+ "inspect",
36
+ image,
37
+ stdout=asyncio.subprocess.PIPE,
38
+ stderr=asyncio.subprocess.PIPE,
39
+ )
40
+ await process.communicate()
41
+ return process.returncode == 0
42
+
43
+
44
+ async def build_default_image(image: str = _DEFAULT_IMAGE) -> bool:
45
+ """Build the default sandbox image from the bundled Dockerfile.
46
+
47
+ Returns ``True`` on success, ``False`` on failure.
48
+ """
49
+ docker = shutil.which("docker") or "docker"
50
+ dockerfile_path = Path(__file__).parent / "Dockerfile"
51
+
52
+ if dockerfile_path.exists():
53
+ cmd = [docker, "build", "-t", image, "-f", str(dockerfile_path), str(dockerfile_path.parent)]
54
+ else:
55
+ # Fallback: pipe Dockerfile content via stdin
56
+ cmd = [docker, "build", "-t", image, "-"]
57
+
58
+ logger.info("Building Docker sandbox image %r ...", image)
59
+
60
+ if dockerfile_path.exists():
61
+ process = await asyncio.create_subprocess_exec(
62
+ *cmd,
63
+ stdout=asyncio.subprocess.PIPE,
64
+ stderr=asyncio.subprocess.PIPE,
65
+ )
66
+ else:
67
+ process = await asyncio.create_subprocess_exec(
68
+ *cmd,
69
+ stdin=asyncio.subprocess.PIPE,
70
+ stdout=asyncio.subprocess.PIPE,
71
+ stderr=asyncio.subprocess.PIPE,
72
+ )
73
+ await process.communicate(input=_DOCKERFILE_CONTENT.encode("utf-8"))
74
+ if process.returncode == 0:
75
+ logger.info("Docker sandbox image %r built successfully", image)
76
+ return True
77
+ logger.warning("Failed to build Docker sandbox image %r", image)
78
+ return False
79
+
80
+ _, stderr_bytes = await process.communicate()
81
+ if process.returncode == 0:
82
+ logger.info("Docker sandbox image %r built successfully", image)
83
+ return True
84
+
85
+ logger.warning(
86
+ "Failed to build Docker sandbox image %r: %s",
87
+ image,
88
+ stderr_bytes.decode("utf-8", errors="replace").strip(),
89
+ )
90
+ return False
91
+
92
+
93
+ async def ensure_image_available(image: str, auto_build: bool) -> bool:
94
+ """Ensure the sandbox image exists, optionally building it.
95
+
96
+ Returns ``True`` if the image is available.
97
+ """
98
+ if await _image_exists(image):
99
+ return True
100
+ if not auto_build:
101
+ logger.warning("Docker image %r not found and auto_build_image is disabled", image)
102
+ return False
103
+ return await build_default_image(image)
@@ -0,0 +1,37 @@
1
+ """Path boundary enforcement for sandbox file operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def validate_sandbox_path(
9
+ path: Path,
10
+ cwd: Path,
11
+ extra_allowed: list[str] | None = None,
12
+ ) -> tuple[bool, str]:
13
+ """Check whether *path* falls within the sandbox boundary.
14
+
15
+ Returns ``(True, "")`` when the path is allowed, or ``(False, reason)``
16
+ when it falls outside the permitted directories.
17
+ """
18
+ resolved = path.resolve()
19
+ resolved_cwd = cwd.resolve()
20
+
21
+ # Primary check: path must be within the project directory
22
+ try:
23
+ resolved.relative_to(resolved_cwd)
24
+ return True, ""
25
+ except ValueError:
26
+ pass
27
+
28
+ # Secondary: check extra allowed paths (from filesystem settings)
29
+ for allowed in extra_allowed or []:
30
+ allowed_path = Path(allowed).expanduser().resolve()
31
+ try:
32
+ resolved.relative_to(allowed_path)
33
+ return True, ""
34
+ except ValueError:
35
+ continue
36
+
37
+ return False, f"path {resolved} is outside the sandbox boundary ({resolved_cwd})"
@@ -0,0 +1,63 @@
1
+ """Module-level Docker sandbox session registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import atexit
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from openharness.config import Settings
12
+ from openharness.sandbox.docker_backend import DockerSandboxSession
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ _active_session: DockerSandboxSession | None = None
17
+
18
+
19
+ def get_docker_sandbox():
20
+ """Return the active Docker sandbox session, or ``None``."""
21
+ return _active_session
22
+
23
+
24
+ def is_docker_sandbox_active() -> bool:
25
+ """Return whether a Docker sandbox session is currently running."""
26
+ return _active_session is not None and _active_session.is_running
27
+
28
+
29
+ async def start_docker_sandbox(
30
+ settings: Settings,
31
+ session_id: str,
32
+ cwd: Path,
33
+ ) -> None:
34
+ """Start a Docker sandbox session for the current OpenHarness session."""
35
+ global _active_session # noqa: PLW0603
36
+
37
+ from openharness.sandbox.docker_backend import DockerSandboxSession, get_docker_availability
38
+
39
+ availability = get_docker_availability(settings)
40
+ if not availability.available:
41
+ if settings.sandbox.fail_if_unavailable:
42
+ from openharness.sandbox.adapter import SandboxUnavailableError
43
+
44
+ raise SandboxUnavailableError(
45
+ availability.reason or "Docker sandbox is unavailable"
46
+ )
47
+ logger.warning("Docker sandbox unavailable: %s", availability.reason)
48
+ return
49
+
50
+ session = DockerSandboxSession(settings=settings, session_id=session_id, cwd=cwd)
51
+ await session.start()
52
+ _active_session = session
53
+
54
+ # Safety net: stop the container if the process exits without close_runtime()
55
+ atexit.register(session.stop_sync)
56
+
57
+
58
+ async def stop_docker_sandbox() -> None:
59
+ """Stop the active Docker sandbox session, if any."""
60
+ global _active_session # noqa: PLW0603
61
+ if _active_session is not None:
62
+ await _active_session.stop()
63
+ _active_session = None
@@ -0,0 +1,30 @@
1
+ """Service exports."""
2
+
3
+ from openharness.services.compact import (
4
+ build_post_compact_messages,
5
+ compact_conversation,
6
+ compact_messages,
7
+ estimate_conversation_tokens,
8
+ summarize_messages,
9
+ )
10
+ from openharness.services.session_storage import (
11
+ export_session_markdown,
12
+ get_project_session_dir,
13
+ load_session_snapshot,
14
+ save_session_snapshot,
15
+ )
16
+ from openharness.services.token_estimation import estimate_message_tokens, estimate_tokens
17
+
18
+ __all__ = [
19
+ "compact_messages",
20
+ "compact_conversation",
21
+ "build_post_compact_messages",
22
+ "estimate_conversation_tokens",
23
+ "estimate_message_tokens",
24
+ "estimate_tokens",
25
+ "export_session_markdown",
26
+ "get_project_session_dir",
27
+ "load_session_snapshot",
28
+ "save_session_snapshot",
29
+ "summarize_messages",
30
+ ]
@@ -0,0 +1,34 @@
1
+ """Automatic memory consolidation (auto-dream)."""
2
+
3
+ from openharness.services.autodream.backup import (
4
+ create_memory_backup,
5
+ diff_memory_dirs,
6
+ format_memory_diff,
7
+ latest_memory_backup,
8
+ restore_memory_backup,
9
+ )
10
+ from openharness.services.autodream.lock import (
11
+ list_sessions_touched_since,
12
+ read_last_consolidated_at,
13
+ record_consolidation,
14
+ rollback_consolidation_lock,
15
+ try_acquire_consolidation_lock,
16
+ )
17
+ from openharness.services.autodream.prompt import build_consolidation_prompt
18
+ from openharness.services.autodream.service import execute_auto_dream, start_dream_now
19
+
20
+ __all__ = [
21
+ "build_consolidation_prompt",
22
+ "create_memory_backup",
23
+ "diff_memory_dirs",
24
+ "execute_auto_dream",
25
+ "format_memory_diff",
26
+ "latest_memory_backup",
27
+ "list_sessions_touched_since",
28
+ "read_last_consolidated_at",
29
+ "record_consolidation",
30
+ "restore_memory_backup",
31
+ "rollback_consolidation_lock",
32
+ "start_dream_now",
33
+ "try_acquire_consolidation_lock",
34
+ ]