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,159 @@
1
+ """Small, shared safety primitives for generated LaTeX and BibTeX."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+
8
+ class LatexSafetyError(ValueError):
9
+ """Raised when document-controlled text is unsafe for the renderer."""
10
+
11
+
12
+ _OBSERVED_RESULT_LANGUAGE = re.compile(
13
+ r"\b(?:we|this\s+(?:study|research|work|investigation|analysis)|"
14
+ r"the\s+(?:study|experiment|analysis|evaluation))\s+(?:have\s+)?"
15
+ r"(?:observed|found|demonstrated|showed|measured|established|confirmed|revealed)\b"
16
+ r"|\b(?:the\s+)?(?:results|experiments|analyses|data|evaluation)\s+(?:have\s+)?"
17
+ r"(?:observed|found|demonstrated|showed|measured|established|confirmed|revealed)\b",
18
+ re.IGNORECASE,
19
+ )
20
+ _FORBIDDEN_TEX_COMMANDS = re.compile(
21
+ r"\\(?:input|include|write|openout|read|catcode|newcommand|renewcommand|def|gdef|"
22
+ r"usepackage|documentclass|immediate|write18|csname|expandafter|every|loop|directlua)\b",
23
+ re.IGNORECASE,
24
+ )
25
+ _MATH_COMMAND = re.compile(r"\\([A-Za-z]+)")
26
+ _ALLOWED_MATH_COMMANDS = {
27
+ "alpha", "beta", "gamma", "delta", "epsilon", "varepsilon", "zeta", "eta", "theta",
28
+ "vartheta", "iota", "kappa", "lambda", "mu", "nu", "xi", "pi", "rho", "sigma",
29
+ "tau", "upsilon", "phi", "varphi", "chi", "psi", "omega", "Gamma", "Delta", "Theta",
30
+ "Lambda", "Xi", "Pi", "Sigma", "Phi", "Psi", "Omega", "frac", "sqrt", "sum", "prod",
31
+ "int", "lim", "log", "ln", "exp", "sin", "cos", "tan", "min", "max", "sup", "inf",
32
+ "forall", "exists", "in", "notin", "subset", "subseteq", "supset", "supseteq", "cup", "cap",
33
+ "to", "rightarrow", "Rightarrow", "Leftarrow", "Leftrightarrow", "leq", "geq", "neq", "approx",
34
+ "equiv", "times", "cdot", "pm", "mp", "partial", "nabla", "infty", "ldots", "dots", "mathrm",
35
+ "mathcal", "mathbb", "mathbf", "operatorname", "left", "right", "langle", "rangle", "vert", "mid",
36
+ }
37
+ _ALLOWED_MATH_CHARS = re.compile(r"^[A-Za-z0-9\s+\-*/=<>(),.;:{}\[\]_\\^|!?'`]+$")
38
+ _MATH_STRUCTURE = re.compile(
39
+ r"(?:[=<>]|[_^]|\\(?:frac|sqrt|sum|prod|int|lim|forall|exists|leq|geq|neq|approx|equiv|to|rightarrow|Rightarrow|Leftarrow|Leftrightarrow)\b)"
40
+ )
41
+ _MATH_IDENTIFIER_COMMAND = re.compile(
42
+ r"\\(?:operatorname|mathrm|mathcal|mathbb|mathbf)\s*\{[A-Za-z]+\}"
43
+ )
44
+ _LOWERCASE_PROSE_WORD = re.compile(r"\b[a-z]{3,}\b")
45
+ _EQUATION_FRAGMENT_BOUNDARY = re.compile(r"\n[ \t]*\n+")
46
+
47
+
48
+ def contains_observed_result_language(value: object) -> bool:
49
+ """Detect proposal-incompatible statements that present work as completed."""
50
+
51
+ return bool(_OBSERVED_RESULT_LANGUAGE.search(str(value or "")))
52
+
53
+
54
+ def normalize_visible_text(value: object, *, label: str) -> str:
55
+ """Normalize visible text while rejecting unsupported control characters.
56
+
57
+ Author content may use any Unicode script. Text safety is enforced by
58
+ escaping and raw-TeX restrictions rather than by a language gate.
59
+ """
60
+
61
+ text = str(value or "")
62
+ if any(ord(character) < 32 and character not in "\n\r\t" for character in text):
63
+ raise LatexSafetyError(f"{label} contains an unsupported control character")
64
+ return text.replace("\r\n", "\n").replace("\r", "\n")
65
+
66
+
67
+ def escape_latex_text(
68
+ value: object,
69
+ *,
70
+ label: str = "text",
71
+ ) -> str:
72
+ """Escape plain visible text; this intentionally never preserves raw TeX."""
73
+
74
+ text = normalize_visible_text(value, label=label)
75
+ replacements = {
76
+ "&": r"\&",
77
+ "%": r"\%",
78
+ "$": r"\$",
79
+ "#": r"\#",
80
+ "_": r"\_",
81
+ "{": r"\{",
82
+ "}": r"\}",
83
+ "~": r"\textasciitilde{}",
84
+ "^": r"\textasciicircum{}",
85
+ "\\": r"\textbackslash{}",
86
+ }
87
+ return "".join(replacements.get(character, character) for character in text)
88
+
89
+
90
+ def safe_math_expression(value: object, *, label: str = "equation") -> str:
91
+ """Allow a deliberately small math-only subset and reject executable TeX."""
92
+
93
+ text = normalize_visible_text(value, label=label).strip()
94
+ if not text:
95
+ raise LatexSafetyError(f"{label} is empty")
96
+ if "$" in text or "\\begin" in text.casefold() or "\\end" in text.casefold():
97
+ raise LatexSafetyError(f"{label} must be a math expression, not a TeX environment")
98
+ if _FORBIDDEN_TEX_COMMANDS.search(text):
99
+ raise LatexSafetyError(f"{label} contains a forbidden TeX command")
100
+ if not _ALLOWED_MATH_CHARS.fullmatch(text):
101
+ raise LatexSafetyError(f"{label} contains unsupported math characters")
102
+ commands = _MATH_COMMAND.findall(text.replace(r"\\", " "))
103
+ unsupported = sorted({command for command in commands if command not in _ALLOWED_MATH_COMMANDS})
104
+ if unsupported:
105
+ raise LatexSafetyError(f"{label} contains unsupported math commands: {', '.join(unsupported)}")
106
+ if not _MATH_STRUCTURE.search(text):
107
+ raise LatexSafetyError(f"{label} must contain a mathematical relation or structure")
108
+ residual_text = _MATH_COMMAND.sub("", _MATH_IDENTIFIER_COMMAND.sub("", text))
109
+ if _LOWERCASE_PROSE_WORD.search(residual_text):
110
+ raise LatexSafetyError(f"{label} contains explanatory prose and must be split from its mathematics")
111
+ return text
112
+
113
+
114
+ def split_equation_content(value: object, *, label: str = "equation") -> list[tuple[str, str]]:
115
+ """Partition an equation block into safe math expressions and visible prose.
116
+
117
+ A model may put sentence-level explanation around otherwise valid formulae.
118
+ Keeping these fragments distinct lets the renderer preserve the explanation
119
+ without sending it into a display-math environment.
120
+ """
121
+
122
+ text = normalize_visible_text(value, label=label).strip()
123
+ if not text:
124
+ raise LatexSafetyError(f"{label} is empty")
125
+ if "$" in text or "\\begin" in text.casefold() or "\\end" in text.casefold():
126
+ raise LatexSafetyError(f"{label} must be a math expression, not a TeX environment")
127
+ if _FORBIDDEN_TEX_COMMANDS.search(text):
128
+ raise LatexSafetyError(f"{label} contains a forbidden TeX command")
129
+ fragments = [
130
+ fragment.strip()
131
+ for fragment in _EQUATION_FRAGMENT_BOUNDARY.split(text)
132
+ if fragment.strip()
133
+ ]
134
+ result: list[tuple[str, str]] = []
135
+ for index, fragment in enumerate(fragments, start=1):
136
+ fragment_label = label if len(fragments) == 1 else f"{label} fragment {index}"
137
+ try:
138
+ result.append(("equation", safe_math_expression(fragment, label=fragment_label)))
139
+ except LatexSafetyError:
140
+ result.append(("prose", fragment))
141
+ return result
142
+
143
+
144
+ def validate_citation_key(value: object) -> str:
145
+ key = str(value or "").strip()
146
+ if not re.fullmatch(r"[A-Za-z0-9._:+-]+", key):
147
+ raise LatexSafetyError(f"citation key is unsafe for BibTeX: {key!r}")
148
+ return key
149
+
150
+
151
+ __all__ = [
152
+ "LatexSafetyError",
153
+ "contains_observed_result_language",
154
+ "escape_latex_text",
155
+ "normalize_visible_text",
156
+ "safe_math_expression",
157
+ "split_equation_content",
158
+ "validate_citation_key",
159
+ ]
@@ -0,0 +1,74 @@
1
+ """Required JSON-only LLM invocation for Research Plan Author stages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping
6
+ from threading import local
7
+ from typing import Any, Optional
8
+
9
+ from src.agents.experiment_design_agent.llm_json import (
10
+ JSON_OBJECT_RESPONSE_FORMAT,
11
+ RequiredJsonLLMError,
12
+ call_required_json,
13
+ )
14
+ from src.llm.provider_registry import resolve_model
15
+
16
+
17
+ def build_author_json_llm_call(
18
+ *,
19
+ config: Any = None,
20
+ model: Optional[str] = None,
21
+ temperature: float | None = None,
22
+ ) -> Callable[..., object]:
23
+ """Build the configured Author callback while preserving strict JSON mode."""
24
+
25
+ holder = local()
26
+
27
+ def setting(value: Any, key: str, default: Any = "") -> Any:
28
+ if isinstance(value, Mapping):
29
+ return value.get(key, default)
30
+ return getattr(value, key, default)
31
+
32
+ def _call(prompt: str, **kwargs: Any) -> object:
33
+ if kwargs.get("response_format", JSON_OBJECT_RESPONSE_FORMAT) != JSON_OBJECT_RESPONSE_FORMAT:
34
+ raise RequiredJsonLLMError("research_plan_author: only JSON object response format is permitted")
35
+ runtime_config = config
36
+ if runtime_config is None:
37
+ from src.config import get_config
38
+
39
+ runtime_config = get_config()
40
+ author_config = setting(runtime_config, "research_plan_author", {})
41
+ authoring_config = setting(author_config, "authoring", {})
42
+ resolved_model = str(model or setting(author_config, "model") or "").strip()
43
+ provider_name = str(setting(author_config, "provider") or "").strip()
44
+ resolved_temperature = float(
45
+ setting(authoring_config, "temperature", 0.5) if temperature is None else temperature
46
+ )
47
+ if model:
48
+ provider_name = resolve_model(runtime_config, resolved_model).provider
49
+ agent = getattr(holder, "agent", None)
50
+ if agent is None:
51
+ from src.agents.idea_agent.agent.base import AgentBase
52
+
53
+ agent = AgentBase(config=runtime_config, provider_name=provider_name or None)
54
+ holder.agent = agent
55
+ if not resolved_model:
56
+ resolved_model = str(agent.provider.default_models.get("author") or "").strip()
57
+ if not resolved_model:
58
+ raise RequiredJsonLLMError("research_plan_author: no model is configured for the author LLM role")
59
+ return agent.chat(
60
+ prompt,
61
+ model=resolved_model,
62
+ response_format=JSON_OBJECT_RESPONSE_FORMAT,
63
+ temperature=resolved_temperature,
64
+ )
65
+
66
+ return _call
67
+
68
+
69
+ __all__ = [
70
+ "JSON_OBJECT_RESPONSE_FORMAT",
71
+ "RequiredJsonLLMError",
72
+ "build_author_json_llm_call",
73
+ "call_required_json",
74
+ ]
@@ -0,0 +1,275 @@
1
+ """Deterministic Markdown views of structured Research Plan documents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ import re
7
+ from typing import Any
8
+
9
+ from .theory_presentation import theory_block_presentation, theory_unit_registry, visible_theory_text
10
+
11
+
12
+ _PRIVATE_PROVENANCE = re.compile(
13
+ r"\[?(?:survey:survey_markdown(?:#section-[A-Za-z0-9._:+-]+)?|anchor:[A-Za-z0-9_:+-]+(?:\.[A-Za-z0-9_:+-]+)*)\]?",
14
+ flags=re.IGNORECASE,
15
+ )
16
+
17
+
18
+ def _mapping(value: object) -> dict[str, Any]:
19
+ return dict(value) if isinstance(value, Mapping) else {}
20
+
21
+
22
+ def _text(value: object) -> str:
23
+ return str(value or "").strip()
24
+
25
+
26
+ def _visible(value: object) -> str:
27
+ return _PRIVATE_PROVENANCE.sub("", _text(value)).strip()
28
+
29
+
30
+ def _visible_for_document(document: Mapping[str, Any], value: object) -> str:
31
+ return _visible(visible_theory_text(document, value))
32
+
33
+
34
+ def _label_component(value: object) -> str:
35
+ component = re.sub(r"[^A-Za-z0-9:-]+", "-", _text(value)).strip("-:")
36
+ return component or "unnamed"
37
+
38
+
39
+ def _equation_label(section_id: object, block_id: object) -> str:
40
+ return f"eq:{_label_component(section_id)}-{_label_component(block_id)}"
41
+
42
+
43
+ def _document_equation_labels(document: Mapping[str, Any]) -> dict[str, str]:
44
+ labels: dict[str, str] = {}
45
+ for section in [*(document.get("sections") or []), *(document.get("appendices") or [])]:
46
+ if not isinstance(section, Mapping):
47
+ continue
48
+ section_id = _text(section.get("section_id"))
49
+ for block in section.get("blocks") or []:
50
+ if isinstance(block, Mapping) and _text(block.get("kind")) == "equation":
51
+ block_id = _text(block.get("block_id"))
52
+ if section_id and block_id:
53
+ labels[f"{section_id}:{block_id}"] = _equation_label(section_id, block_id)
54
+ return labels
55
+
56
+
57
+ def _equation_cross_references(block: Mapping[str, Any], *, equation_labels: Mapping[str, str]) -> str:
58
+ labels = [
59
+ equation_labels[reference_id]
60
+ for raw_reference_id in block.get("reference_block_ids") or []
61
+ if (reference_id := _text(raw_reference_id)) in equation_labels
62
+ ]
63
+ return "" if not labels else " See Eq. (" + ", ".join(labels) + ")."
64
+
65
+
66
+ def _claim_map(document: Mapping[str, Any]) -> dict[str, Mapping[str, Any]]:
67
+ return {
68
+ _text(claim.get("claim_id")): claim
69
+ for claim in document.get("claim_provenance") or []
70
+ if isinstance(claim, Mapping) and _text(claim.get("claim_id"))
71
+ }
72
+
73
+
74
+ def _citation_suffix(block: Mapping[str, Any], claims: Mapping[str, Mapping[str, Any]]) -> str:
75
+ keys: list[str] = []
76
+ for claim_id in block.get("claim_ids") or []:
77
+ claim = claims.get(_text(claim_id))
78
+ if not isinstance(claim, Mapping):
79
+ continue
80
+ for citation_key in claim.get("citation_keys") or []:
81
+ normalized = _text(citation_key)
82
+ if normalized and normalized not in keys:
83
+ keys.append(normalized)
84
+ return " " + " ".join(f"[@{key}]" for key in keys) if keys else ""
85
+
86
+
87
+ def _block_markdown(
88
+ block: Mapping[str, Any],
89
+ *,
90
+ claims: Mapping[str, Mapping[str, Any]],
91
+ evaluation_markers: bool,
92
+ section_id: str,
93
+ document: Mapping[str, Any],
94
+ theory_registry: Mapping[str, Mapping[str, Any]],
95
+ equation_labels: Mapping[str, str],
96
+ ) -> str:
97
+ block_id = _text(block.get("block_id"))
98
+ marker = f"<!-- author:section={section_id} block={block_id} -->\n" if evaluation_markers else ""
99
+ heading = _visible_for_document(document, block.get("heading"))
100
+ kind = _text(block.get("kind"))
101
+ text = _visible_for_document(document, block.get("text"))
102
+ if not text:
103
+ return ""
104
+ prefix = f"### {heading}\n\n" if heading else ""
105
+ suffix = _citation_suffix(block, claims)
106
+ theory_prefix, _status = theory_block_presentation(
107
+ block,
108
+ claims=claims,
109
+ registry=theory_registry,
110
+ )
111
+ cross_references = _equation_cross_references(block, equation_labels=equation_labels)
112
+ if kind == "equation":
113
+ equation_label = equation_labels.get(_text(block.get("block_id")), "")
114
+ equation_prefix = f"**Equation [{equation_label}].**\n\n" if equation_label else ""
115
+ return marker + prefix + equation_prefix + "$$\n" + text + "\n$$" + suffix
116
+ if kind == "table" and theory_prefix:
117
+ return marker + prefix + f"**{theory_prefix}**\n\n" + text + cross_references + suffix
118
+ if theory_prefix:
119
+ return marker + prefix + f"**{theory_prefix}** " + text + cross_references + suffix
120
+ if kind == "definition":
121
+ return marker + prefix + "**Definition.** " + text + cross_references + suffix
122
+ if kind == "proposition":
123
+ return marker + prefix + "**Proposition (Candidate).** " + text + cross_references + suffix
124
+ if kind == "protocol":
125
+ return marker + prefix + "**Planned protocol.** " + text + cross_references + suffix
126
+ if kind == "outcome_branch":
127
+ return marker + prefix + "**Pre-registered Branch (Expected---Not Observed).** " + text + cross_references + suffix
128
+ if kind == "review_checklist":
129
+ return marker + prefix + "**Human-review checklist (Review-required).** " + text + cross_references + suffix
130
+ return marker + prefix + text + cross_references + suffix
131
+
132
+
133
+ def _section_markdown(
134
+ section: Mapping[str, Any],
135
+ *,
136
+ claims: Mapping[str, Mapping[str, Any]],
137
+ evaluation_markers: bool,
138
+ document: Mapping[str, Any],
139
+ theory_registry: Mapping[str, Mapping[str, Any]],
140
+ document_equation_labels: Mapping[str, str],
141
+ ) -> str:
142
+ section_id = _text(section.get("section_id"))
143
+ title = _visible_for_document(document, section.get("title"))
144
+ equation_labels = {
145
+ **document_equation_labels,
146
+ **{
147
+ _text(block.get("block_id")): _equation_label(section_id, block.get("block_id"))
148
+ for block in section.get("blocks") or []
149
+ if isinstance(block, Mapping) and _text(block.get("kind")) == "equation"
150
+ },
151
+ }
152
+ lines = [f"## {title}"]
153
+ for block in section.get("blocks") or []:
154
+ if isinstance(block, Mapping):
155
+ rendered = _block_markdown(
156
+ block,
157
+ claims=claims,
158
+ evaluation_markers=evaluation_markers,
159
+ section_id=section_id,
160
+ document=document,
161
+ theory_registry=theory_registry,
162
+ equation_labels=equation_labels,
163
+ )
164
+ if rendered:
165
+ lines.append(rendered)
166
+ return "\n\n".join(lines)
167
+
168
+
169
+ def _bibliography_markdown(document: Mapping[str, Any], claims: Mapping[str, Mapping[str, Any]]) -> str:
170
+ cited_keys = {
171
+ _text(key)
172
+ for claim in claims.values()
173
+ for key in claim.get("citation_keys") or []
174
+ if _text(key)
175
+ }
176
+ records = [
177
+ dict(record)
178
+ for record in document.get("citation_registry") or []
179
+ if isinstance(record, Mapping) and _text(record.get("citation_key")) in cited_keys
180
+ ]
181
+ lines = ["## References"]
182
+ if not records:
183
+ lines.append("No registered sources are cited in the visible manuscript.")
184
+ return "\n\n".join(lines)
185
+ for record in sorted(records, key=lambda item: _text(item.get("citation_key"))):
186
+ metadata = _mapping(record.get("bibliographic_metadata"))
187
+ authors = [_text(author) for author in metadata.get("authors") or [] if _text(author)]
188
+ author_text = authors[0] + (" et al." if len(authors) > 1 else "") if authors else "Registered source"
189
+ title = _visible(metadata.get("title")) or "Untitled work"
190
+ venue = _visible(metadata.get("venue"))
191
+ year = _visible(metadata.get("year"))
192
+ tail = ", ".join(item for item in (venue, year) if item)
193
+ entry = f"- [@{_text(record.get('citation_key'))}] {author_text}. *{title}*."
194
+ lines.append(entry + (f" {tail}." if tail else ""))
195
+ return "\n".join(lines)
196
+
197
+
198
+ def render_research_plan_markdown(
199
+ document: Mapping[str, Any],
200
+ *,
201
+ evaluation_markers: bool = False,
202
+ compact_references: bool = False,
203
+ ) -> str:
204
+ """Render a complete, source-safe Markdown manuscript from one document."""
205
+
206
+ claims = _claim_map(document)
207
+ theory_registry = theory_unit_registry(document)
208
+ document_equation_labels = _document_equation_labels(document)
209
+ metadata = _mapping(document.get("document_metadata"))
210
+ title = _visible_for_document(document, metadata.get("title")) or "Research Plan"
211
+ lines = [f"# {title}"]
212
+ keywords = [
213
+ _visible_for_document(document, keyword)
214
+ for keyword in document.get("keywords") or []
215
+ if _visible_for_document(document, keyword)
216
+ ]
217
+ if keywords:
218
+ lines.append("**Keywords:** " + ", ".join(keywords))
219
+ abstract = _mapping(document.get("abstract"))
220
+ abstract_text = _visible_for_document(document, abstract.get("text"))
221
+ if abstract_text:
222
+ lines.append("## Abstract\n\n" + abstract_text)
223
+ for section in document.get("sections") or []:
224
+ if not isinstance(section, Mapping) or _text(section.get("section_id")) == "references":
225
+ continue
226
+ lines.append(
227
+ _section_markdown(
228
+ section,
229
+ claims=claims,
230
+ evaluation_markers=evaluation_markers,
231
+ document=document,
232
+ theory_registry=theory_registry,
233
+ document_equation_labels=document_equation_labels,
234
+ )
235
+ )
236
+ appendices = [section for section in document.get("appendices") or [] if isinstance(section, Mapping)]
237
+ if appendices:
238
+ lines.append("# Appendices")
239
+ lines.extend(
240
+ _section_markdown(
241
+ section,
242
+ claims=claims,
243
+ evaluation_markers=evaluation_markers,
244
+ document=document,
245
+ theory_registry=theory_registry,
246
+ document_equation_labels=document_equation_labels,
247
+ )
248
+ for section in appendices
249
+ )
250
+ if compact_references:
251
+ cited_count = len(
252
+ {
253
+ _text(key)
254
+ for claim in claims.values()
255
+ for key in claim.get("citation_keys") or []
256
+ if _text(key)
257
+ }
258
+ )
259
+ lines.append(f"## References\n\nThe manuscript uses {cited_count} registered citation key(s).")
260
+ else:
261
+ lines.append(_bibliography_markdown(document, claims))
262
+ return "\n\n".join(part for part in lines if part.strip()).strip() + "\n"
263
+
264
+
265
+ def render_quality_review_markdown(document: Mapping[str, Any]) -> str:
266
+ """Render the full prose with private stable markers for quality review only."""
267
+
268
+ return render_research_plan_markdown(
269
+ document,
270
+ evaluation_markers=True,
271
+ compact_references=True,
272
+ )
273
+
274
+
275
+ __all__ = ["render_quality_review_markdown", "render_research_plan_markdown"]
@@ -0,0 +1,149 @@
1
+ """Structural and first-page render validation for generated Author PDFs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ import subprocess
9
+ import tempfile
10
+ from time import perf_counter
11
+ from typing import Any
12
+
13
+
14
+ PDF_VALIDATION_SCHEMA_VERSION = "research_plan_author_pdf_validation_v1"
15
+
16
+
17
+ class PdfValidationError(RuntimeError):
18
+ """Raised when a generated PDF cannot be parsed or rendered safely."""
19
+
20
+ def __init__(self, message: str, *, report: dict[str, Any] | None = None) -> None:
21
+ super().__init__(message)
22
+ self.report = dict(report) if isinstance(report, dict) else None
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class PdfValidationResult:
27
+ valid: bool
28
+ report: dict[str, Any]
29
+
30
+
31
+ def validate_pdf(
32
+ pdf_path: str | Path,
33
+ *,
34
+ renderer: str | Path,
35
+ timeout_seconds: int = 60,
36
+ logger: Any | None = None,
37
+ ) -> PdfValidationResult:
38
+ """Require a parseable PDF with at least one renderable first page."""
39
+
40
+ source = Path(pdf_path).expanduser().resolve()
41
+ renderer_path = Path(renderer).expanduser().resolve()
42
+ if not source.is_file() or source.stat().st_size == 0:
43
+ raise PdfValidationError(f"generated PDF does not exist or is empty: {source}")
44
+ if not renderer_path.is_file():
45
+ raise PdfValidationError(f"PDF renderer is not an executable file: {renderer_path}")
46
+ parser_report: dict[str, Any] = {}
47
+ try:
48
+ from pypdf import PdfReader
49
+
50
+ reader = PdfReader(str(source))
51
+ page_count = len(reader.pages)
52
+ if page_count < 1:
53
+ raise PdfValidationError("generated PDF has no pages")
54
+ first_page = reader.pages[0]
55
+ box = first_page.mediabox
56
+ width = float(box.width)
57
+ height = float(box.height)
58
+ if width <= 0 or height <= 0:
59
+ raise PdfValidationError("generated PDF has invalid first-page dimensions")
60
+ extracted = ""
61
+ for page in reader.pages[: min(page_count, 3)]:
62
+ try:
63
+ extracted += page.extract_text() or ""
64
+ except Exception:
65
+ continue
66
+ parser_report = {
67
+ "backend": "pypdf",
68
+ "status": "parsed",
69
+ "page_count": page_count,
70
+ "first_page_width": width,
71
+ "first_page_height": height,
72
+ "extractable_text": bool(extracted.strip()),
73
+ "warning": "non_text_pdf" if not extracted.strip() else "",
74
+ }
75
+ except PdfValidationError:
76
+ raise
77
+ except Exception as error:
78
+ raise PdfValidationError(
79
+ f"pypdf could not parse generated PDF: {type(error).__name__}: {error}",
80
+ report={
81
+ "schema_version": PDF_VALIDATION_SCHEMA_VERSION,
82
+ "generated_at": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
83
+ "valid": False,
84
+ "pdf_path": str(source),
85
+ "file_size_bytes": source.stat().st_size,
86
+ "parser": {"backend": "pypdf", "status": "parse_failed", "error": type(error).__name__},
87
+ "renderer": {"backend": "pdftoppm", "status": "not_started"},
88
+ },
89
+ ) from error
90
+ with tempfile.TemporaryDirectory(prefix="research-plan-author-pdf-") as temporary_root:
91
+ prefix = Path(temporary_root) / "page"
92
+ command = [str(renderer_path), "-f", "1", "-l", "1", "-png", str(source), str(prefix)]
93
+ started_at = perf_counter()
94
+ if logger is not None:
95
+ logger.emit("pdf_validation", "render_started", status="RUNNING", renderer=str(renderer_path))
96
+ try:
97
+ completed = subprocess.run(
98
+ command,
99
+ shell=False,
100
+ capture_output=True,
101
+ text=True,
102
+ encoding="utf-8",
103
+ errors="replace",
104
+ timeout=max(1, int(timeout_seconds)),
105
+ check=False,
106
+ )
107
+ except subprocess.TimeoutExpired as error:
108
+ raise PdfValidationError(f"PDF first-page renderer timed out after {timeout_seconds} seconds") from error
109
+ except OSError as error:
110
+ raise PdfValidationError(f"PDF first-page renderer could not start: {error}") from error
111
+ images = sorted(Path(temporary_root).glob("page-*.png"))
112
+ render_success = completed.returncode == 0 and bool(images) and images[0].stat().st_size > 0
113
+ render_report = {
114
+ "backend": "pdftoppm",
115
+ "status": "rendered" if render_success else "render_failed",
116
+ "command": [str(value) for value in command],
117
+ "return_code": completed.returncode,
118
+ "elapsed_ms": round((perf_counter() - started_at) * 1000, 3),
119
+ "rendered_first_page_bytes": images[0].stat().st_size if images else 0,
120
+ "stderr_tail": (completed.stderr or "")[-2000:],
121
+ }
122
+ if logger is not None:
123
+ logger.emit(
124
+ "pdf_validation",
125
+ "render_completed" if render_success else "render_failed",
126
+ level="INFO" if render_success else "ERROR",
127
+ status="COMPLETED" if render_success else "FAILED",
128
+ return_code=completed.returncode,
129
+ elapsed_ms=render_report["elapsed_ms"],
130
+ )
131
+ report = {
132
+ "schema_version": PDF_VALIDATION_SCHEMA_VERSION,
133
+ "generated_at": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
134
+ "valid": render_success,
135
+ "pdf_path": str(source),
136
+ "file_size_bytes": source.stat().st_size,
137
+ "parser": parser_report,
138
+ "renderer": render_report,
139
+ }
140
+ if not render_success:
141
+ raise PdfValidationError(
142
+ "PDF first-page render validation failed: "
143
+ + (completed.stderr or completed.stdout or "renderer produced no PNG")[-1000:],
144
+ report=report,
145
+ )
146
+ return PdfValidationResult(valid=True, report=report)
147
+
148
+
149
+ __all__ = ["PDF_VALIDATION_SCHEMA_VERSION", "PdfValidationError", "PdfValidationResult", "validate_pdf"]