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,223 @@
1
+ """Project a bounded, source-anchored Idea evolution appendix without invention."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from copy import deepcopy
6
+ from pathlib import Path
7
+ from typing import Any, Mapping
8
+
9
+ from src.agents.experiment_design_agent.idea_intake import load_idea_artifact_bundle
10
+
11
+
12
+ IDEA_EVOLUTION_APPENDIX_SCHEMA_VERSION = "idea_evolution_appendix_v1"
13
+
14
+
15
+ class IdeaEvolutionError(ValueError):
16
+ """Raised when the requested Idea history conflicts with the selected direction."""
17
+
18
+
19
+ def _mapping(value: object) -> dict[str, Any]:
20
+ return dict(value) if isinstance(value, Mapping) else {}
21
+
22
+
23
+ def _text(value: object) -> str:
24
+ return str(value or "").strip()
25
+
26
+
27
+ def _identifier_candidates(direction: Mapping[str, Any]) -> set[str]:
28
+ values = (
29
+ direction.get("id"),
30
+ direction.get("direction_id"),
31
+ direction.get("direction_mode"),
32
+ direction.get("title"),
33
+ )
34
+ return {_text(value) for value in values if _text(value)}
35
+
36
+
37
+ def _selected_direction(idea_result: Mapping[str, Any], selected_direction_id: str) -> tuple[dict[str, Any], str]:
38
+ directions = idea_result.get("directions")
39
+ candidates = directions if isinstance(directions, list) else []
40
+ for index, raw_direction in enumerate(candidates):
41
+ direction = _mapping(raw_direction)
42
+ if selected_direction_id in _identifier_candidates(direction):
43
+ return direction, f"/directions/{index}"
44
+ primary_direction = _text(idea_result.get("primary_direction"))
45
+ if selected_direction_id == primary_direction:
46
+ for index, raw_direction in enumerate(candidates):
47
+ direction = _mapping(raw_direction)
48
+ if primary_direction in _identifier_candidates(direction):
49
+ return direction, f"/directions/{index}"
50
+ raise IdeaEvolutionError(
51
+ f"selected direction mismatch: '{selected_direction_id}' is not present in idea_result.json directions"
52
+ )
53
+
54
+
55
+ def _snapshot(record: Mapping[str, Any]) -> dict[str, Any]:
56
+ allowed_fields = (
57
+ "id",
58
+ "direction_id",
59
+ "direction_mode",
60
+ "title",
61
+ "research_question",
62
+ "central_hypothesis",
63
+ "hypothesis",
64
+ "mechanism_or_relation",
65
+ "contribution",
66
+ "contributions",
67
+ "claim_scope",
68
+ "boundary_conditions",
69
+ "alternative_explanations",
70
+ "falsifiers",
71
+ "known_unknowns",
72
+ "target_gap_ids",
73
+ )
74
+ return {
75
+ field: deepcopy(record[field])
76
+ for field in allowed_fields
77
+ if field in record and record[field] not in (None, "", [], {})
78
+ }
79
+
80
+
81
+ def _append_checkpoint(
82
+ checkpoints: list[dict[str, Any]],
83
+ *,
84
+ source_file: str,
85
+ source_path: str,
86
+ source_json_pointer: str,
87
+ record: Mapping[str, Any],
88
+ ) -> None:
89
+ snapshot = _snapshot(record)
90
+ if not snapshot:
91
+ return
92
+ if any(item["snapshot"] == snapshot for item in checkpoints):
93
+ return
94
+ checkpoints.append(
95
+ {
96
+ "checkpoint_id": f"checkpoint-{len(checkpoints) + 1}",
97
+ "checkpoint_type": "available_audit_snapshot",
98
+ "temporal_order": "unknown",
99
+ "source_file": source_file,
100
+ "source_path": source_path,
101
+ "source_json_pointer": source_json_pointer,
102
+ "snapshot": snapshot,
103
+ }
104
+ )
105
+
106
+
107
+ def project_idea_evolution(
108
+ idea_result_path: str | Path,
109
+ *,
110
+ selected_direction_id: str,
111
+ max_iterations: int = 3,
112
+ ) -> dict[str, Any]:
113
+ """Project available Idea audit snapshots without inventing an iteration history.
114
+
115
+ The common Idea artifacts are parallel audit outputs from one run. Their
116
+ filenames do not establish chronology or parentage, so they remain neutral
117
+ source checkpoints unless a future producer supplies auditable evolution
118
+ metadata.
119
+ """
120
+
121
+ if max_iterations not in {2, 3}:
122
+ raise ValueError("max_iterations must be either 2 or 3")
123
+ expected_direction_id = _text(selected_direction_id)
124
+ if not expected_direction_id:
125
+ raise IdeaEvolutionError("selected_direction_id is required for Idea evolution projection")
126
+ try:
127
+ bundle = load_idea_artifact_bundle(idea_result_path)
128
+ except (FileNotFoundError, ValueError) as error:
129
+ raise IdeaEvolutionError(str(error)) from error
130
+ idea_result = _mapping(bundle.get("idea_result"))
131
+ selected, selected_pointer = _selected_direction(idea_result, expected_direction_id)
132
+ source_paths = _mapping(bundle.get("source_paths"))
133
+ audit_sources = _mapping(bundle.get("audit_sources"))
134
+ checkpoints: list[dict[str, Any]] = []
135
+ candidate = _mapping(audit_sources.get("idea_candidate"))
136
+ _append_checkpoint(
137
+ checkpoints,
138
+ source_file="idea_candidate.json",
139
+ source_path=_text(source_paths.get("idea_candidate")),
140
+ source_json_pointer="/",
141
+ record=candidate,
142
+ )
143
+ portfolio = _mapping(audit_sources.get("idea_portfolio"))
144
+ selected_primary_idea = _mapping(
145
+ audit_sources.get("selected_primary_idea") or portfolio.get("selected_primary_idea")
146
+ )
147
+ _append_checkpoint(
148
+ checkpoints,
149
+ source_file="idea_portfolio.json",
150
+ source_path=_text(source_paths.get("idea_portfolio")),
151
+ source_json_pointer="/selected_primary_idea",
152
+ record=selected_primary_idea,
153
+ )
154
+ _append_checkpoint(
155
+ checkpoints,
156
+ source_file="idea_result.json",
157
+ source_path=_text(source_paths.get("idea_result")),
158
+ source_json_pointer=selected_pointer,
159
+ record=selected,
160
+ )
161
+ if len(checkpoints) > max_iterations:
162
+ checkpoints = [checkpoints[0], checkpoints[-1]] if max_iterations == 2 else checkpoints[-max_iterations:]
163
+ for index, checkpoint in enumerate(checkpoints, start=1):
164
+ checkpoint["checkpoint_id"] = f"checkpoint-{index}"
165
+ return {
166
+ "schema_version": IDEA_EVOLUTION_APPENDIX_SCHEMA_VERSION,
167
+ "status": "CHECKPOINTS_AVAILABLE" if len(checkpoints) >= 2 else "INSUFFICIENT_HISTORY",
168
+ "selected_direction_id": expected_direction_id,
169
+ "max_iterations": max_iterations,
170
+ "temporal_order": "unknown",
171
+ "checkpoints": checkpoints,
172
+ "iterations": [],
173
+ "missing_audit_sources": list(bundle.get("missing_sources") or []),
174
+ "authoring_constraints": {
175
+ "language": "en",
176
+ "do_not_invent_missing_iterations": True,
177
+ "do_not_describe_checkpoints_as_temporal_iterations": True,
178
+ "temporal_order_is_unknown_without_iteration_id_timestamp_parent_or_auditable_diff": True,
179
+ "do_not_present_idea_history_as_empirical_evidence": True,
180
+ "all_displayed_checkpoints_require_source_anchors": True,
181
+ },
182
+ }
183
+
184
+
185
+ def disabled_idea_evolution() -> dict[str, Any]:
186
+ """Represent an explicit CLI opt-out without pretending history was unavailable."""
187
+
188
+ return {
189
+ "schema_version": IDEA_EVOLUTION_APPENDIX_SCHEMA_VERSION,
190
+ "status": "DISABLED",
191
+ "selected_direction_id": "",
192
+ "max_iterations": 0,
193
+ "temporal_order": "not_applicable",
194
+ "checkpoints": [],
195
+ "iterations": [],
196
+ "missing_audit_sources": [],
197
+ "authoring_constraints": {
198
+ "language": "en",
199
+ "do_not_invent_missing_iterations": True,
200
+ "do_not_describe_checkpoints_as_temporal_iterations": True,
201
+ "temporal_order_is_unknown_without_iteration_id_timestamp_parent_or_auditable_diff": True,
202
+ "do_not_present_idea_history_as_empirical_evidence": True,
203
+ "all_displayed_checkpoints_require_source_anchors": True,
204
+ },
205
+ }
206
+
207
+
208
+ def unavailable_idea_evolution(reason: str) -> dict[str, Any]:
209
+ """Expose missing optional history explicitly rather than fabricating an appendix."""
210
+
211
+ payload = disabled_idea_evolution()
212
+ payload["status"] = "UNAVAILABLE"
213
+ payload["unavailable_reason"] = _text(reason)
214
+ return payload
215
+
216
+
217
+ __all__ = [
218
+ "IDEA_EVOLUTION_APPENDIX_SCHEMA_VERSION",
219
+ "IdeaEvolutionError",
220
+ "disabled_idea_evolution",
221
+ "project_idea_evolution",
222
+ "unavailable_idea_evolution",
223
+ ]
@@ -0,0 +1,76 @@
1
+ """Load and validate a canonical ExperimentDesign Author handoff."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import hashlib
7
+ from pathlib import Path
8
+ from typing import Any, Mapping
9
+
10
+ from .contracts import validate_author_input
11
+
12
+
13
+ class AuthorInputLoadError(ValueError):
14
+ """Raised when an Author handoff cannot safely enter the writing workflow."""
15
+
16
+
17
+ def load_author_input_with_identity(path: str | Path) -> tuple[Path, dict[str, Any], dict[str, Any]]:
18
+ """Read, validate, snapshot, and identify one handoff from the same bytes."""
19
+
20
+ resolved_path = Path(path).expanduser().resolve()
21
+ if not resolved_path.is_file():
22
+ raise AuthorInputLoadError(f"Research Plan Author input is not a file: {resolved_path}")
23
+ try:
24
+ raw_bytes = resolved_path.read_bytes()
25
+ except OSError as error:
26
+ raise AuthorInputLoadError(f"Cannot read Research Plan Author input '{resolved_path}': {error}") from error
27
+ try:
28
+ payload = json.loads(raw_bytes.decode("utf-8"))
29
+ except json.JSONDecodeError as error:
30
+ raise AuthorInputLoadError(f"Research Plan Author input is not valid JSON: {resolved_path}: {error}") from error
31
+ except UnicodeDecodeError as error:
32
+ raise AuthorInputLoadError(f"Research Plan Author input is not UTF-8 JSON: {resolved_path}: {error}") from error
33
+ if not isinstance(payload, Mapping):
34
+ raise AuthorInputLoadError("Research Plan Author input must contain one JSON object")
35
+ errors = validate_author_input(payload)
36
+ if errors:
37
+ raise AuthorInputLoadError("Research Plan Author input validation failed: " + "; ".join(errors))
38
+ return (
39
+ resolved_path,
40
+ dict(payload),
41
+ {
42
+ "path": str(resolved_path),
43
+ "sha256": hashlib.sha256(raw_bytes).hexdigest(),
44
+ "byte_size": len(raw_bytes),
45
+ },
46
+ )
47
+
48
+
49
+ def load_author_input(path: str | Path) -> tuple[Path, dict[str, Any]]:
50
+ """Read one explicit Author JSON artifact without selecting an ambiguous run file."""
51
+
52
+ resolved_path, payload, _identity = load_author_input_with_identity(path)
53
+ return resolved_path, payload
54
+
55
+
56
+ def identify_author_input(path: str | Path) -> dict[str, Any]:
57
+ """Return a stable identity for the exact handoff bytes that were loaded."""
58
+
59
+ resolved_path = Path(path).expanduser().resolve()
60
+ try:
61
+ raw_bytes = resolved_path.read_bytes()
62
+ except OSError as error:
63
+ raise AuthorInputLoadError(f"Cannot identify Research Plan Author input '{resolved_path}': {error}") from error
64
+ return {
65
+ "path": str(resolved_path),
66
+ "sha256": hashlib.sha256(raw_bytes).hexdigest(),
67
+ "byte_size": len(raw_bytes),
68
+ }
69
+
70
+
71
+ __all__ = [
72
+ "AuthorInputLoadError",
73
+ "identify_author_input",
74
+ "load_author_input",
75
+ "load_author_input_with_identity",
76
+ ]
@@ -0,0 +1,260 @@
1
+ """Isolated, shell-free LaTeX/BibTeX compilation for Author render projects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime, timezone
7
+ import os
8
+ from pathlib import Path
9
+ import shutil
10
+ import subprocess
11
+ import tempfile
12
+ from time import perf_counter
13
+ from collections.abc import Mapping
14
+ from typing import Any
15
+
16
+
17
+ LATEX_COMPILE_SCHEMA_VERSION = "research_plan_author_latex_compile_v1"
18
+
19
+
20
+ class LatexCompilerError(RuntimeError):
21
+ """Raised when a required LaTeX executable cannot be resolved safely."""
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class LatexCompileResult:
26
+ success: bool
27
+ report: dict[str, Any]
28
+ log_text: str
29
+ staged_pdf: Path | None
30
+
31
+
32
+ def _text(value: object) -> str:
33
+ return str(value or "").strip()
34
+
35
+
36
+ def _absolute_executable_path(value: str | Path) -> Path:
37
+ """Make an executable path absolute without dereferencing its command name.
38
+
39
+ LaTeX distributions frequently expose ``pdflatex`` and ``xelatex`` as
40
+ symlinks to lower-level binaries. The invoked filename selects the TeX
41
+ format, so resolving that symlink changes the command's behavior.
42
+ """
43
+
44
+ return Path(value).expanduser().absolute()
45
+
46
+
47
+ def resolve_executable(
48
+ *,
49
+ explicit: str | Path | None,
50
+ environment_variable: str,
51
+ configured: str | Path | None,
52
+ fallback: str,
53
+ label: str,
54
+ ) -> Path:
55
+ """Resolve one executable in declared precedence order; a supplied bad path fails."""
56
+
57
+ candidates = (
58
+ ("explicit", _text(explicit)),
59
+ (f"environment:{environment_variable}", _text(os.environ.get(environment_variable))),
60
+ ("configuration", _text(configured)),
61
+ ("PATH", fallback),
62
+ )
63
+ for source, candidate in candidates:
64
+ if not candidate:
65
+ continue
66
+ path = Path(candidate).expanduser()
67
+ has_path_syntax = path.is_absolute() or any(separator in candidate for separator in ("/", "\\"))
68
+ if has_path_syntax:
69
+ executable_path = _absolute_executable_path(path)
70
+ if executable_path.is_file():
71
+ return executable_path
72
+ if source != "PATH":
73
+ raise LatexCompilerError(f"{label} from {source} is not an executable file: {executable_path}")
74
+ continue
75
+ located = shutil.which(candidate)
76
+ if located:
77
+ return _absolute_executable_path(located)
78
+ if source != "PATH":
79
+ raise LatexCompilerError(f"{label} from {source} is not available on PATH: {candidate}")
80
+ raise LatexCompilerError(
81
+ f"{label} is unavailable; set {environment_variable}, pass the matching CLI option, or configure it explicitly"
82
+ )
83
+
84
+
85
+ def _safe_command(command: list[str]) -> list[str]:
86
+ return [str(argument).replace("\n", " ").replace("\r", " ")[:2000] for argument in command]
87
+
88
+
89
+ def _run_command(command: list[str], *, cwd: Path, timeout_seconds: int) -> tuple[dict[str, Any], str]:
90
+ started_at = perf_counter()
91
+ safe = _safe_command(command)
92
+ try:
93
+ completed = subprocess.run(
94
+ command,
95
+ cwd=str(cwd),
96
+ shell=False,
97
+ capture_output=True,
98
+ text=True,
99
+ encoding="utf-8",
100
+ errors="replace",
101
+ timeout=max(1, int(timeout_seconds)),
102
+ check=False,
103
+ )
104
+ output = (completed.stdout or "") + ("\n" if completed.stdout and completed.stderr else "") + (completed.stderr or "")
105
+ return (
106
+ {
107
+ "command": safe,
108
+ "return_code": completed.returncode,
109
+ "timed_out": False,
110
+ "elapsed_ms": round((perf_counter() - started_at) * 1000, 3),
111
+ },
112
+ output,
113
+ )
114
+ except subprocess.TimeoutExpired as error:
115
+ stdout = error.stdout.decode("utf-8", "replace") if isinstance(error.stdout, bytes) else str(error.stdout or "")
116
+ stderr = error.stderr.decode("utf-8", "replace") if isinstance(error.stderr, bytes) else str(error.stderr or "")
117
+ return (
118
+ {
119
+ "command": safe,
120
+ "return_code": None,
121
+ "timed_out": True,
122
+ "elapsed_ms": round((perf_counter() - started_at) * 1000, 3),
123
+ },
124
+ stdout + ("\n" if stdout and stderr else "") + stderr,
125
+ )
126
+ except OSError as error:
127
+ return (
128
+ {
129
+ "command": safe,
130
+ "return_code": None,
131
+ "timed_out": False,
132
+ "elapsed_ms": round((perf_counter() - started_at) * 1000, 3),
133
+ "execution_error": type(error).__name__,
134
+ },
135
+ str(error),
136
+ )
137
+
138
+
139
+ def compile_latex_project(
140
+ project_dir: str | Path,
141
+ *,
142
+ main_tex: str | Path,
143
+ latex_engine: str | Path,
144
+ bibtex: str | Path | None,
145
+ run_bibtex: bool,
146
+ timeout_seconds: int,
147
+ staged_pdf_path: str | Path,
148
+ logger: Any | None = None,
149
+ ) -> LatexCompileResult:
150
+ """Compile an isolated copy and stage a PDF only after all passes succeed."""
151
+
152
+ source_project = Path(project_dir).expanduser().resolve()
153
+ source_main = Path(main_tex).expanduser().resolve()
154
+ target_pdf = Path(staged_pdf_path).expanduser().resolve()
155
+ try:
156
+ main_relative = source_main.relative_to(source_project)
157
+ except ValueError as error:
158
+ raise LatexCompilerError("main TeX file must remain inside the render project") from error
159
+ if not source_project.is_dir() or not source_main.is_file():
160
+ raise LatexCompilerError("render project or main TeX file does not exist")
161
+ engine_path = _absolute_executable_path(latex_engine)
162
+ bibtex_path = _absolute_executable_path(bibtex) if bibtex else None
163
+ if not engine_path.is_file():
164
+ raise LatexCompilerError(f"LaTeX engine is not an executable file: {engine_path}")
165
+ if run_bibtex and (bibtex_path is None or not bibtex_path.is_file()):
166
+ raise LatexCompilerError("BibTeX is required by emitted references but is not an executable file")
167
+ base_name = main_relative.stem
168
+ commands: list[tuple[str, list[str]]] = [
169
+ (
170
+ "latex_initial",
171
+ [str(engine_path), "-interaction=nonstopmode", "-halt-on-error", "-no-shell-escape", main_relative.name],
172
+ )
173
+ ]
174
+ if run_bibtex:
175
+ commands.append(("bibtex", [str(bibtex_path), base_name]))
176
+ commands.extend(
177
+ [
178
+ (
179
+ "latex_resolve_references_1",
180
+ [str(engine_path), "-interaction=nonstopmode", "-halt-on-error", "-no-shell-escape", main_relative.name],
181
+ ),
182
+ (
183
+ "latex_resolve_references_2",
184
+ [str(engine_path), "-interaction=nonstopmode", "-halt-on-error", "-no-shell-escape", main_relative.name],
185
+ ),
186
+ ]
187
+ )
188
+ command_reports: list[dict[str, Any]] = []
189
+ log_parts: list[str] = []
190
+ failure_stage = ""
191
+ with tempfile.TemporaryDirectory(prefix="research-plan-author-compile-") as temporary_root:
192
+ workspace = Path(temporary_root) / "project"
193
+ shutil.copytree(source_project, workspace)
194
+ compile_cwd = workspace / main_relative.parent
195
+ for stage, command in commands:
196
+ if logger is not None:
197
+ logger.emit("latex_compile", "command_started", status="RUNNING", compile_stage=stage, command=_safe_command(command))
198
+ report, output = _run_command(command, cwd=compile_cwd, timeout_seconds=timeout_seconds)
199
+ report["stage"] = stage
200
+ command_reports.append(report)
201
+ log_parts.extend(
202
+ [
203
+ f"===== {stage} =====",
204
+ "COMMAND: " + " ".join(report["command"]),
205
+ output.rstrip(),
206
+ "",
207
+ ]
208
+ )
209
+ successful = report.get("return_code") == 0 and not report.get("timed_out")
210
+ if logger is not None:
211
+ logger.emit(
212
+ "latex_compile",
213
+ "command_completed" if successful else "command_failed",
214
+ level="INFO" if successful else "ERROR",
215
+ status="COMPLETED" if successful else "FAILED",
216
+ compile_stage=stage,
217
+ return_code=report.get("return_code"),
218
+ timed_out=report.get("timed_out"),
219
+ elapsed_ms=report.get("elapsed_ms"),
220
+ )
221
+ if not successful:
222
+ failure_stage = stage
223
+ break
224
+ compiled_pdf = compile_cwd / f"{base_name}.pdf"
225
+ success = not failure_stage and compiled_pdf.is_file() and compiled_pdf.stat().st_size > 0
226
+ if not success and not failure_stage:
227
+ failure_stage = "pdf_missing"
228
+ log_parts.append("Compilation commands completed but the expected PDF was not produced.\n")
229
+ staged: Path | None = None
230
+ if success:
231
+ target_pdf.parent.mkdir(parents=True, exist_ok=True)
232
+ if target_pdf.exists():
233
+ raise LatexCompilerError(f"staged PDF target already exists: {target_pdf}")
234
+ temporary_pdf = target_pdf.with_name(f".{target_pdf.name}.{os.getpid()}.tmp")
235
+ shutil.copy2(compiled_pdf, temporary_pdf)
236
+ os.replace(temporary_pdf, target_pdf)
237
+ staged = target_pdf
238
+ report = {
239
+ "schema_version": LATEX_COMPILE_SCHEMA_VERSION,
240
+ "generated_at": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
241
+ "success": bool(success),
242
+ "failure_stage": failure_stage,
243
+ "project_dir": str(source_project),
244
+ "main_tex": str(source_main),
245
+ "run_bibtex": bool(run_bibtex),
246
+ "timeout_seconds": int(timeout_seconds),
247
+ "allow_shell_escape": False,
248
+ "commands": command_reports,
249
+ "staged_pdf": str(staged) if staged is not None else "",
250
+ }
251
+ return LatexCompileResult(success=bool(success), report=report, log_text="\n".join(log_parts), staged_pdf=staged)
252
+
253
+
254
+ __all__ = [
255
+ "LATEX_COMPILE_SCHEMA_VERSION",
256
+ "LatexCompileResult",
257
+ "LatexCompilerError",
258
+ "compile_latex_project",
259
+ "resolve_executable",
260
+ ]