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,799 @@
1
+ """Shared runtime assembly for headless and Textual UIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any, Awaitable, Callable, Iterable
10
+
11
+ from openharness.api.client import AnthropicApiClient, SupportsStreamingMessages
12
+ from openharness.api.codex_client import CodexApiClient
13
+ from openharness.api.copilot_client import CopilotClient
14
+ from openharness.api.openai_client import OpenAICompatibleClient
15
+ from openharness.api.provider import auth_status, detect_provider
16
+ from openharness.bridge import get_bridge_manager
17
+ from openharness.commands import (
18
+ CommandContext,
19
+ CommandResult,
20
+ MemoryCommandBackend,
21
+ create_default_command_registry,
22
+ lookup_skill_slash_command,
23
+ )
24
+ from openharness.config import get_config_file_path, load_settings
25
+ from openharness.engine import QueryEngine
26
+ from openharness.engine.messages import (
27
+ ConversationMessage,
28
+ ToolResultBlock,
29
+ ToolUseBlock,
30
+ sanitize_conversation_messages,
31
+ )
32
+ from openharness.engine.query import MaxTurnsExceeded
33
+ from openharness.engine.stream_events import StreamEvent
34
+ from openharness.hooks import HookEvent, HookExecutionContext, HookExecutor, load_hook_registry
35
+ from openharness.hooks.hot_reload import HookReloader
36
+ from openharness.mcp.client import McpClientManager
37
+ from openharness.mcp.config import load_mcp_server_configs
38
+ from openharness.permissions import PermissionChecker
39
+ from openharness.plugins import load_plugins
40
+ from openharness.prompts import build_runtime_system_prompt
41
+ from openharness.state import AppState, AppStateStore
42
+ from openharness.services.session_backend import DEFAULT_SESSION_BACKEND, SessionBackend
43
+ from openharness.tools import ToolRegistry, create_default_tool_registry
44
+ from openharness.keybindings import load_keybindings
45
+
46
+ PermissionPrompt = Callable[[str, str], Awaitable[bool]]
47
+ AskUserPrompt = Callable[[str], Awaitable[str]]
48
+ EditApprovalPrompt = Callable[[str, str, int, int], Awaitable[str]]
49
+ SystemPrinter = Callable[[str], Awaitable[None]]
50
+ StreamRenderer = Callable[[StreamEvent], Awaitable[None]]
51
+ ClearHandler = Callable[[], Awaitable[None]]
52
+
53
+
54
+ def _resolve_image_generation_config(settings) -> dict[str, str]:
55
+ """Resolve image generation configuration from settings, environment, and Codex auth."""
56
+ from openharness.config.settings import ImageGenerationConfig, ProviderProfile
57
+
58
+ cfg = settings.image_generation
59
+ env_cfg = ImageGenerationConfig.from_env()
60
+ resolved = {
61
+ "provider": cfg.provider or env_cfg.provider,
62
+ "model": cfg.model or env_cfg.model,
63
+ "api_key": cfg.api_key or env_cfg.api_key,
64
+ "base_url": cfg.base_url or env_cfg.base_url,
65
+ "codex_model": cfg.codex_model or env_cfg.codex_model,
66
+ "codex_base_url": cfg.codex_base_url or env_cfg.codex_base_url,
67
+ }
68
+
69
+ try:
70
+ codex_profile = settings.merged_profiles().get("codex") or ProviderProfile(
71
+ label="Codex Subscription",
72
+ provider="openai_codex",
73
+ api_format="openai",
74
+ auth_source="codex_subscription",
75
+ default_model="gpt-5.4",
76
+ )
77
+ codex_settings = settings.model_copy(
78
+ update={
79
+ "active_profile": "codex",
80
+ "profiles": {**settings.profiles, "codex": codex_profile},
81
+ }
82
+ ).materialize_active_profile()
83
+ codex_auth = codex_settings.resolve_auth()
84
+ resolved["codex_auth_token"] = codex_auth.value
85
+ resolved["codex_base_url"] = resolved["codex_base_url"] or (codex_settings.base_url or "")
86
+ resolved["codex_model"] = resolved["codex_model"] or codex_settings.model
87
+ except Exception:
88
+ pass
89
+
90
+ return resolved
91
+
92
+
93
+ def _resolve_vision_config(settings) -> dict[str, str]:
94
+ """Resolve the vision model configuration from settings or environment.
95
+
96
+ Priority: settings.vision fields > environment variables > empty.
97
+ """
98
+ from openharness.config.settings import VisionModelConfig
99
+
100
+ cfg = settings.vision
101
+ if cfg.is_configured:
102
+ return {
103
+ "model": cfg.model,
104
+ "api_key": cfg.api_key,
105
+ "base_url": cfg.base_url,
106
+ }
107
+
108
+ # Fall back to environment variables
109
+ env_cfg = VisionModelConfig.from_env()
110
+ if env_cfg.is_configured:
111
+ return {
112
+ "model": env_cfg.model,
113
+ "api_key": env_cfg.api_key,
114
+ "base_url": env_cfg.base_url,
115
+ }
116
+
117
+ return {}
118
+
119
+
120
+ @dataclass
121
+ class RuntimeBundle:
122
+ """Shared runtime objects for one interactive session."""
123
+
124
+ api_client: SupportsStreamingMessages
125
+ cwd: str
126
+ mcp_manager: McpClientManager
127
+ tool_registry: ToolRegistry
128
+ app_state: AppStateStore
129
+ hook_executor: HookExecutor
130
+ engine: QueryEngine
131
+ commands: object
132
+ external_api_client: bool
133
+ enforce_max_turns: bool = True
134
+ session_id: str = ""
135
+ settings_overrides: dict[str, Any] = field(default_factory=dict)
136
+ session_backend: SessionBackend = DEFAULT_SESSION_BACKEND
137
+ extra_skill_dirs: tuple[str, ...] = ()
138
+ extra_plugin_roots: tuple[str, ...] = ()
139
+ memory_backend: MemoryCommandBackend | None = None
140
+ include_project_memory: bool = True
141
+ autodream_context: dict[str, object] | None = None
142
+
143
+ def current_settings(self):
144
+ """Return the effective settings for this session.
145
+
146
+ We persist most settings to disk (``~/.openharness/settings.json``), but
147
+ CLI options like ``--model``/``--api-format`` should remain in effect for
148
+ the lifetime of the running process. Without this overlay, issuing any
149
+ slash command (e.g. ``/fast``) would refresh UI state from disk and
150
+ "snap back" the model/provider to whatever is stored in the config file.
151
+ """
152
+ return load_settings().merge_cli_overrides(**self.settings_overrides)
153
+
154
+ def current_plugins(self):
155
+ """Return currently visible plugins for the working tree."""
156
+ return load_plugins(
157
+ self.current_settings(),
158
+ self.cwd,
159
+ extra_roots=self.extra_plugin_roots,
160
+ )
161
+
162
+ def hook_summary(self) -> str:
163
+ """Return the current hook summary."""
164
+ return load_hook_registry(self.current_settings(), self.current_plugins()).summary()
165
+
166
+ def plugin_summary(self) -> str:
167
+ """Return the current plugin summary."""
168
+ plugins = self.current_plugins()
169
+ if not plugins:
170
+ return "No plugins discovered."
171
+ lines = ["Plugins:"]
172
+ for plugin in plugins:
173
+ state = "enabled" if plugin.enabled else "disabled"
174
+ lines.append(f"- {plugin.manifest.name} [{state}] {plugin.manifest.description}")
175
+ return "\n".join(lines)
176
+
177
+ def mcp_summary(self) -> str:
178
+ """Return the current MCP summary."""
179
+ statuses = self.mcp_manager.list_statuses()
180
+ if not statuses:
181
+ return "No MCP servers configured."
182
+ lines = ["MCP servers:"]
183
+ for status in statuses:
184
+ suffix = f" - {status.detail}" if status.detail else ""
185
+ lines.append(f"- {status.name}: {status.state}{suffix}")
186
+ if status.tools:
187
+ lines.append(f" tools: {', '.join(tool.name for tool in status.tools)}")
188
+ if status.resources:
189
+ lines.append(f" resources: {', '.join(resource.uri for resource in status.resources)}")
190
+ return "\n".join(lines)
191
+
192
+
193
+ def _resolve_api_client_from_settings(settings) -> SupportsStreamingMessages:
194
+ """Build the appropriate API client for the resolved settings."""
195
+ # Ensure profile fields (base_url, model, api_format) are projected to settings
196
+ settings = settings.materialize_active_profile()
197
+
198
+ def _safe_resolve_auth():
199
+ try:
200
+ return settings.resolve_auth()
201
+ except Exception as exc:
202
+ _print_auth_resolution_error(settings, exc)
203
+ raise SystemExit(1)
204
+
205
+ if settings.api_format == "copilot":
206
+ from openharness.api.copilot_client import COPILOT_DEFAULT_MODEL
207
+
208
+ copilot_model = (
209
+ COPILOT_DEFAULT_MODEL
210
+ if settings.model in {"claude-sonnet-4-20250514", "claude-sonnet-4-6", "sonnet", "default"}
211
+ else settings.model
212
+ )
213
+ return CopilotClient(model=copilot_model)
214
+ if settings.provider == "openai_codex":
215
+ auth = _safe_resolve_auth()
216
+ return CodexApiClient(
217
+ auth_token=auth.value,
218
+ base_url=settings.base_url,
219
+ )
220
+ if settings.provider == "anthropic_claude":
221
+ return AnthropicApiClient(
222
+ auth_token=_safe_resolve_auth().value,
223
+ base_url=settings.base_url,
224
+ claude_oauth=True,
225
+ auth_token_resolver=lambda: settings.resolve_auth().value,
226
+ )
227
+ if settings.api_format in ("openai", "openai_compat"):
228
+ auth = _safe_resolve_auth()
229
+ return OpenAICompatibleClient(
230
+ api_key=auth.value,
231
+ base_url=settings.base_url,
232
+ timeout=settings.timeout,
233
+ )
234
+ auth = _safe_resolve_auth()
235
+ return AnthropicApiClient(
236
+ api_key=auth.value,
237
+ base_url=settings.base_url,
238
+ )
239
+
240
+
241
+ def _print_auth_resolution_error(settings, exc: Exception) -> None:
242
+ """Render auth failures without collapsing subscription errors into API-key advice."""
243
+ try:
244
+ profile_name, profile = settings.resolve_profile()
245
+ auth_source = (getattr(profile, "auth_source", "") or "").strip()
246
+ except Exception:
247
+ profile_name = ""
248
+ auth_source = ""
249
+
250
+ message = str(exc).strip() or exc.__class__.__name__
251
+ if auth_source in {"claude_subscription", "codex_subscription"}:
252
+ login_command = "claude-login" if auth_source == "claude_subscription" else "codex-login"
253
+ provider_name = profile_name or (
254
+ "claude-subscription" if auth_source == "claude_subscription" else "codex"
255
+ )
256
+ print(
257
+ f"Error: {message}\n"
258
+ f" This profile uses subscription auth, not an API key.\n"
259
+ f" Run `oh auth {login_command}` to bind the local CLI session, then\n"
260
+ f" run `oh provider use {provider_name}` to activate it.",
261
+ file=sys.stderr,
262
+ )
263
+ return
264
+
265
+ print(
266
+ "Error: No API key configured.\n"
267
+ f" {message}\n"
268
+ " Run `oh auth login` to set up authentication, or set the\n"
269
+ " ANTHROPIC_API_KEY (or OPENAI_API_KEY) environment variable.",
270
+ file=sys.stderr,
271
+ )
272
+
273
+
274
+ async def build_runtime(
275
+ *,
276
+ prompt: str | None = None,
277
+ cwd: str | None = None,
278
+ model: str | None = None,
279
+ max_turns: int | None = None,
280
+ effort: str | None = None,
281
+ base_url: str | None = None,
282
+ system_prompt: str | None = None,
283
+ api_key: str | None = None,
284
+ api_format: str | None = None,
285
+ active_profile: str | None = None,
286
+ api_client: SupportsStreamingMessages | None = None,
287
+ permission_prompt: PermissionPrompt | None = None,
288
+ ask_user_prompt: AskUserPrompt | None = None,
289
+ edit_approval_prompt: EditApprovalPrompt | None = None,
290
+ restore_messages: list[dict] | None = None,
291
+ restore_tool_metadata: dict[str, object] | None = None,
292
+ enforce_max_turns: bool = True,
293
+ session_backend: SessionBackend | None = None,
294
+ permission_mode: str | None = None,
295
+ extra_skill_dirs: Iterable[str | Path] | None = None,
296
+ extra_plugin_roots: Iterable[str | Path] | None = None,
297
+ memory_backend: MemoryCommandBackend | None = None,
298
+ include_project_memory: bool = True,
299
+ autodream_context: dict[str, object] | None = None,
300
+ ) -> RuntimeBundle:
301
+ """Build the shared runtime for an OpenHarness session."""
302
+ settings_overrides: dict[str, Any] = {
303
+ "model": model,
304
+ "max_turns": max_turns,
305
+ "effort": effort,
306
+ "base_url": base_url,
307
+ "system_prompt": system_prompt,
308
+ "api_key": api_key,
309
+ "api_format": api_format,
310
+ "active_profile": active_profile,
311
+ "permission_mode": permission_mode,
312
+ }
313
+ settings = load_settings().merge_cli_overrides(**settings_overrides)
314
+ cwd = str(Path(cwd).expanduser().resolve()) if cwd else str(Path.cwd())
315
+ normalized_skill_dirs = tuple(str(Path(path).expanduser().resolve()) for path in (extra_skill_dirs or ()))
316
+ normalized_plugin_roots = tuple(str(Path(path).expanduser().resolve()) for path in (extra_plugin_roots or ()))
317
+ plugins = load_plugins(settings, cwd, extra_roots=normalized_plugin_roots)
318
+ if api_client:
319
+ resolved_api_client = api_client
320
+ else:
321
+ resolved_api_client = _resolve_api_client_from_settings(settings)
322
+ mcp_manager = McpClientManager(load_mcp_server_configs(settings, plugins))
323
+ await mcp_manager.connect_all()
324
+ tool_registry = create_default_tool_registry(mcp_manager)
325
+ # Register plugin-provided tools
326
+ for plugin in plugins:
327
+ if plugin.enabled and plugin.tools:
328
+ for tool in plugin.tools:
329
+ tool_registry.register(tool)
330
+ provider = detect_provider(settings)
331
+ bridge_manager = get_bridge_manager()
332
+ app_state = AppStateStore(
333
+ AppState(
334
+ # Show the effective runtime model (after CLI/env/profile merges),
335
+ # not profile.last_model which may be stale.
336
+ model=settings.model,
337
+ permission_mode=settings.permission.mode.value,
338
+ theme=settings.theme,
339
+ cwd=cwd,
340
+ provider=provider.name,
341
+ auth_status=auth_status(settings),
342
+ base_url=settings.base_url or "",
343
+ vim_enabled=settings.vim_mode,
344
+ voice_enabled=settings.voice_mode,
345
+ voice_available=provider.voice_supported,
346
+ voice_reason=provider.voice_reason,
347
+ fast_mode=settings.fast_mode,
348
+ effort=settings.effort,
349
+ passes=settings.passes,
350
+ mcp_connected=sum(1 for status in mcp_manager.list_statuses() if status.state == "connected"),
351
+ mcp_failed=sum(1 for status in mcp_manager.list_statuses() if status.state == "failed"),
352
+ bridge_sessions=len(bridge_manager.list_sessions()),
353
+ output_style=settings.output_style,
354
+ keybindings=load_keybindings(),
355
+ )
356
+ )
357
+ hook_reloader = HookReloader(get_config_file_path())
358
+ hook_executor = HookExecutor(
359
+ hook_reloader.current_registry() if api_client is None else load_hook_registry(settings, plugins),
360
+ HookExecutionContext(
361
+ cwd=Path(cwd).resolve(),
362
+ api_client=resolved_api_client,
363
+ default_model=settings.model,
364
+ ),
365
+ )
366
+ engine_max_turns = settings.max_turns if (enforce_max_turns or max_turns is not None) else None
367
+ system_prompt_text = build_runtime_system_prompt(
368
+ settings,
369
+ cwd=cwd,
370
+ latest_user_prompt=prompt,
371
+ extra_skill_dirs=normalized_skill_dirs,
372
+ extra_plugin_roots=normalized_plugin_roots,
373
+ include_project_memory=include_project_memory,
374
+ )
375
+ from uuid import uuid4
376
+
377
+ session_id = uuid4().hex[:12]
378
+
379
+ restored_metadata = {
380
+ "permission_mode": settings.permission.mode.value,
381
+ "read_file_state": [],
382
+ "invoked_skills": [],
383
+ "async_agent_state": [],
384
+ "async_agent_tasks": [],
385
+ "recent_work_log": [],
386
+ "recent_verified_work": [],
387
+ "task_focus_state": {
388
+ "goal": "",
389
+ "recent_goals": [],
390
+ "active_artifacts": [],
391
+ "verified_state": [],
392
+ "next_step": "",
393
+ },
394
+ "compact_checkpoints": [],
395
+ }
396
+ if isinstance(restore_tool_metadata, dict):
397
+ for key, value in restore_tool_metadata.items():
398
+ restored_metadata[key] = value
399
+
400
+ engine = QueryEngine(
401
+ api_client=resolved_api_client,
402
+ tool_registry=tool_registry,
403
+ permission_checker=PermissionChecker(settings.permission),
404
+ cwd=cwd,
405
+ model=settings.model,
406
+ system_prompt=system_prompt_text,
407
+ max_tokens=settings.max_tokens,
408
+ context_window_tokens=settings.context_window_tokens or settings.memory.context_window_tokens,
409
+ auto_compact_threshold_tokens=(
410
+ settings.auto_compact_threshold_tokens
411
+ or settings.memory.auto_compact_threshold_tokens
412
+ ),
413
+ max_turns=engine_max_turns,
414
+ permission_prompt=permission_prompt,
415
+ ask_user_prompt=ask_user_prompt,
416
+ hook_executor=hook_executor,
417
+ settings=settings,
418
+ tool_metadata={
419
+ "mcp_manager": mcp_manager,
420
+ "bridge_manager": bridge_manager,
421
+ "extra_skill_dirs": normalized_skill_dirs,
422
+ "extra_plugin_roots": normalized_plugin_roots,
423
+ "session_id": session_id,
424
+ "edit_approval_prompt": edit_approval_prompt,
425
+ "vision_model_config": _resolve_vision_config(settings),
426
+ "image_generation_config": _resolve_image_generation_config(settings),
427
+ **restored_metadata,
428
+ },
429
+ )
430
+ if autodream_context is not None:
431
+ engine.tool_metadata["autodream_context"] = autodream_context
432
+ # Restore messages from a saved session if provided
433
+ if restore_messages:
434
+ restored = sanitize_conversation_messages(
435
+ [ConversationMessage.model_validate(m) for m in restore_messages]
436
+ )
437
+ engine.load_messages(restored)
438
+
439
+ # Start Docker sandbox if configured
440
+ if settings.sandbox.enabled and settings.sandbox.backend == "docker":
441
+ from openharness.sandbox.session import start_docker_sandbox
442
+
443
+ await start_docker_sandbox(settings, session_id, Path(cwd))
444
+
445
+ return RuntimeBundle(
446
+ api_client=resolved_api_client,
447
+ cwd=cwd,
448
+ mcp_manager=mcp_manager,
449
+ tool_registry=tool_registry,
450
+ app_state=app_state,
451
+ hook_executor=hook_executor,
452
+ engine=engine,
453
+ commands=create_default_command_registry(
454
+ plugin_commands=[
455
+ command
456
+ for plugin in plugins
457
+ if plugin.enabled
458
+ for command in plugin.commands
459
+ ]
460
+ ),
461
+ external_api_client=api_client is not None,
462
+ enforce_max_turns=enforce_max_turns or max_turns is not None,
463
+ session_id=session_id,
464
+ settings_overrides=settings_overrides,
465
+ session_backend=session_backend or DEFAULT_SESSION_BACKEND,
466
+ extra_skill_dirs=normalized_skill_dirs,
467
+ extra_plugin_roots=normalized_plugin_roots,
468
+ memory_backend=memory_backend,
469
+ include_project_memory=include_project_memory,
470
+ autodream_context=autodream_context,
471
+ )
472
+
473
+
474
+ async def start_runtime(bundle: RuntimeBundle) -> None:
475
+ """Run session start hooks."""
476
+ await bundle.hook_executor.execute(
477
+ HookEvent.SESSION_START,
478
+ {"cwd": bundle.cwd, "event": HookEvent.SESSION_START.value},
479
+ )
480
+
481
+
482
+ async def close_runtime(bundle: RuntimeBundle) -> None:
483
+ """Close runtime-owned resources."""
484
+ from openharness.sandbox.session import stop_docker_sandbox
485
+
486
+ await stop_docker_sandbox()
487
+ # Extract local environment rules from session before closing
488
+ try:
489
+ from openharness.personalization.session_hook import update_rules_from_session
490
+ update_rules_from_session(bundle.engine.messages)
491
+ except Exception:
492
+ pass # personalization is best-effort, never block session end
493
+
494
+ await bundle.mcp_manager.close()
495
+ await bundle.hook_executor.execute(
496
+ HookEvent.SESSION_END,
497
+ {"cwd": bundle.cwd, "event": HookEvent.SESSION_END.value},
498
+ )
499
+ close_api_client = getattr(bundle.api_client, "close", None)
500
+ if close_api_client is not None:
501
+ await close_api_client()
502
+
503
+
504
+ def _last_user_text(messages: list[ConversationMessage]) -> str:
505
+ for msg in reversed(messages):
506
+ if msg.role == "user" and msg.text.strip():
507
+ return msg.text.strip()
508
+ return ""
509
+
510
+
511
+ def _truncate(text: str, limit: int) -> str:
512
+ if len(text) <= limit:
513
+ return text
514
+ return text[:limit] + "…"
515
+
516
+
517
+ def _format_pending_tool_results(messages: list[ConversationMessage]) -> str | None:
518
+ """Render a compact summary when we stop after tool execution but before the follow-up model turn."""
519
+ if not messages:
520
+ return None
521
+
522
+ last = messages[-1]
523
+ if last.role != "user":
524
+ return None
525
+ tool_results = [block for block in last.content if isinstance(block, ToolResultBlock)]
526
+ if not tool_results:
527
+ return None
528
+
529
+ tool_uses_by_id: dict[str, ToolUseBlock] = {}
530
+ assistant_text = ""
531
+ for msg in reversed(messages[:-1]):
532
+ if msg.role != "assistant":
533
+ continue
534
+ if not msg.tool_uses:
535
+ continue
536
+ assistant_text = msg.text.strip()
537
+ for tu in msg.tool_uses:
538
+ tool_uses_by_id[tu.id] = tu
539
+ break
540
+
541
+ lines: list[str] = [
542
+ "Pending continuation: tool results were produced, but the model did not get a chance to respond yet."
543
+ ]
544
+ if assistant_text:
545
+ lines.append(f"Last assistant message: {_truncate(assistant_text, 400)}")
546
+
547
+ max_results = 3
548
+ for tr in tool_results[:max_results]:
549
+ tu = tool_uses_by_id.get(tr.tool_use_id)
550
+ if tu is not None:
551
+ raw_input = json.dumps(tu.input, ensure_ascii=True, sort_keys=True)
552
+ lines.append(
553
+ f"- {tu.name} {_truncate(raw_input, 200)} -> {_truncate(tr.content.strip(), 400)}"
554
+ )
555
+ else:
556
+ lines.append(
557
+ f"- tool_result[{tr.tool_use_id}] -> {_truncate(tr.content.strip(), 400)}"
558
+ )
559
+
560
+ if len(tool_results) > max_results:
561
+ lines.append(f"(+{len(tool_results) - max_results} more tool results)")
562
+
563
+ lines.append("To continue from these results, run: /continue [COUNT].")
564
+ return "\n".join(lines)
565
+
566
+
567
+ def sync_app_state(bundle: RuntimeBundle) -> None:
568
+ """Refresh UI state from current settings and dynamic keybindings."""
569
+ settings = bundle.current_settings()
570
+ if bundle.enforce_max_turns:
571
+ bundle.engine.set_max_turns(settings.max_turns)
572
+ provider = detect_provider(settings)
573
+ bundle.app_state.set(
574
+ model=settings.model,
575
+ permission_mode=settings.permission.mode.value,
576
+ theme=settings.theme,
577
+ cwd=bundle.cwd,
578
+ provider=provider.name,
579
+ auth_status=auth_status(settings),
580
+ base_url=settings.base_url or "",
581
+ vim_enabled=settings.vim_mode,
582
+ voice_enabled=settings.voice_mode,
583
+ voice_available=provider.voice_supported,
584
+ voice_reason=provider.voice_reason,
585
+ fast_mode=settings.fast_mode,
586
+ effort=settings.effort,
587
+ passes=settings.passes,
588
+ mcp_connected=sum(1 for status in bundle.mcp_manager.list_statuses() if status.state == "connected"),
589
+ mcp_failed=sum(1 for status in bundle.mcp_manager.list_statuses() if status.state == "failed"),
590
+ bridge_sessions=len(get_bridge_manager().list_sessions()),
591
+ output_style=settings.output_style,
592
+ keybindings=load_keybindings(),
593
+ )
594
+
595
+
596
+ def refresh_runtime_client(bundle: RuntimeBundle) -> None:
597
+ """Refresh the active runtime client after provider/auth/profile changes."""
598
+ settings = bundle.current_settings()
599
+ if not bundle.external_api_client:
600
+ bundle.api_client = _resolve_api_client_from_settings(settings)
601
+ bundle.engine.set_api_client(bundle.api_client)
602
+ bundle.hook_executor.update_context(
603
+ api_client=bundle.api_client,
604
+ default_model=settings.model,
605
+ )
606
+ bundle.engine.set_model(settings.model)
607
+ bundle.engine.set_effort(settings.effort)
608
+ bundle.engine.set_permission_checker(PermissionChecker(settings.permission))
609
+ system_prompt = build_runtime_system_prompt(
610
+ settings,
611
+ cwd=bundle.cwd,
612
+ latest_user_prompt=_last_user_text(bundle.engine.messages),
613
+ extra_skill_dirs=bundle.extra_skill_dirs,
614
+ extra_plugin_roots=bundle.extra_plugin_roots,
615
+ include_project_memory=bundle.include_project_memory,
616
+ )
617
+ bundle.engine.set_system_prompt(system_prompt)
618
+ sync_app_state(bundle)
619
+
620
+
621
+ async def handle_line(
622
+ bundle: RuntimeBundle,
623
+ line: str,
624
+ *,
625
+ print_system: SystemPrinter,
626
+ render_event: StreamRenderer,
627
+ clear_output: ClearHandler,
628
+ user_message: ConversationMessage | None = None,
629
+ ) -> bool:
630
+ """Handle one submitted line for either headless or TUI rendering."""
631
+ if not bundle.external_api_client:
632
+ bundle.hook_executor.update_registry(
633
+ load_hook_registry(bundle.current_settings(), bundle.current_plugins())
634
+ )
635
+
636
+ command_context = CommandContext(
637
+ engine=bundle.engine,
638
+ hooks_summary=bundle.hook_summary(),
639
+ mcp_summary=bundle.mcp_summary(),
640
+ plugin_summary=bundle.plugin_summary(),
641
+ cwd=bundle.cwd,
642
+ tool_registry=bundle.tool_registry,
643
+ app_state=bundle.app_state,
644
+ session_backend=bundle.session_backend,
645
+ session_id=bundle.session_id,
646
+ extra_skill_dirs=bundle.extra_skill_dirs,
647
+ extra_plugin_roots=bundle.extra_plugin_roots,
648
+ memory_backend=bundle.memory_backend,
649
+ include_project_memory=bundle.include_project_memory,
650
+ )
651
+ parsed = None if user_message is not None else (
652
+ bundle.commands.lookup(line) or lookup_skill_slash_command(line, command_context)
653
+ )
654
+ if parsed is not None:
655
+ command, args = parsed
656
+ result = await command.handler(
657
+ args,
658
+ command_context,
659
+ )
660
+ if result.refresh_runtime:
661
+ refresh_runtime_client(bundle)
662
+ await _render_command_result(result, print_system, clear_output, render_event)
663
+ if result.submit_prompt is not None:
664
+ original_model = bundle.engine.model
665
+ if result.submit_model:
666
+ bundle.engine.set_model(result.submit_model)
667
+ settings = bundle.current_settings()
668
+ submit_prompt = result.submit_prompt
669
+ system_prompt = build_runtime_system_prompt(
670
+ settings,
671
+ cwd=bundle.cwd,
672
+ latest_user_prompt=submit_prompt,
673
+ extra_skill_dirs=bundle.extra_skill_dirs,
674
+ extra_plugin_roots=bundle.extra_plugin_roots,
675
+ include_project_memory=bundle.include_project_memory,
676
+ )
677
+ bundle.engine.set_system_prompt(system_prompt)
678
+ try:
679
+ async for event in bundle.engine.submit_message(submit_prompt):
680
+ await render_event(event)
681
+ except MaxTurnsExceeded as exc:
682
+ await print_system(f"Stopped after {exc.max_turns} turns (max_turns).")
683
+ pending = _format_pending_tool_results(bundle.engine.messages)
684
+ if pending:
685
+ await print_system(pending)
686
+ finally:
687
+ if result.submit_model:
688
+ bundle.engine.set_model(original_model)
689
+ bundle.session_backend.save_snapshot(
690
+ cwd=bundle.cwd,
691
+ model=bundle.engine.model,
692
+ system_prompt=system_prompt,
693
+ messages=bundle.engine.messages,
694
+ usage=bundle.engine.total_usage,
695
+ session_id=bundle.session_id,
696
+ tool_metadata=bundle.engine.tool_metadata,
697
+ )
698
+ if result.continue_pending:
699
+ settings = bundle.current_settings()
700
+ if bundle.enforce_max_turns:
701
+ bundle.engine.set_max_turns(settings.max_turns)
702
+ system_prompt = build_runtime_system_prompt(
703
+ settings,
704
+ cwd=bundle.cwd,
705
+ latest_user_prompt=_last_user_text(bundle.engine.messages),
706
+ extra_skill_dirs=bundle.extra_skill_dirs,
707
+ extra_plugin_roots=bundle.extra_plugin_roots,
708
+ include_project_memory=bundle.include_project_memory,
709
+ )
710
+ bundle.engine.set_system_prompt(system_prompt)
711
+ turns = result.continue_turns if result.continue_turns is not None else bundle.engine.max_turns
712
+ try:
713
+ async for event in bundle.engine.continue_pending(max_turns=turns):
714
+ await render_event(event)
715
+ except MaxTurnsExceeded as exc:
716
+ await print_system(f"Stopped after {exc.max_turns} turns (max_turns).")
717
+ pending = _format_pending_tool_results(bundle.engine.messages)
718
+ if pending:
719
+ await print_system(pending)
720
+ bundle.session_backend.save_snapshot(
721
+ cwd=bundle.cwd,
722
+ model=settings.model,
723
+ system_prompt=system_prompt,
724
+ messages=bundle.engine.messages,
725
+ usage=bundle.engine.total_usage,
726
+ session_id=bundle.session_id,
727
+ tool_metadata=bundle.engine.tool_metadata,
728
+ )
729
+ sync_app_state(bundle)
730
+ return not result.should_exit
731
+
732
+ settings = bundle.current_settings()
733
+ if bundle.enforce_max_turns:
734
+ bundle.engine.set_max_turns(settings.max_turns)
735
+ latest_user_prompt = line or (user_message.text if user_message is not None else "")
736
+ system_prompt = build_runtime_system_prompt(
737
+ settings,
738
+ cwd=bundle.cwd,
739
+ latest_user_prompt=latest_user_prompt,
740
+ extra_skill_dirs=bundle.extra_skill_dirs,
741
+ extra_plugin_roots=bundle.extra_plugin_roots,
742
+ include_project_memory=bundle.include_project_memory,
743
+ )
744
+ bundle.engine.set_system_prompt(system_prompt)
745
+ try:
746
+ async for event in bundle.engine.submit_message(user_message or line):
747
+ await render_event(event)
748
+ except MaxTurnsExceeded as exc:
749
+ await print_system(f"Stopped after {exc.max_turns} turns (max_turns).")
750
+ pending = _format_pending_tool_results(bundle.engine.messages)
751
+ if pending:
752
+ await print_system(pending)
753
+ bundle.session_backend.save_snapshot(
754
+ cwd=bundle.cwd,
755
+ model=settings.model,
756
+ system_prompt=system_prompt,
757
+ messages=bundle.engine.messages,
758
+ usage=bundle.engine.total_usage,
759
+ session_id=bundle.session_id,
760
+ tool_metadata=bundle.engine.tool_metadata,
761
+ )
762
+ sync_app_state(bundle)
763
+ return True
764
+ bundle.session_backend.save_snapshot(
765
+ cwd=bundle.cwd,
766
+ model=settings.model,
767
+ system_prompt=system_prompt,
768
+ messages=bundle.engine.messages,
769
+ usage=bundle.engine.total_usage,
770
+ session_id=bundle.session_id,
771
+ tool_metadata=bundle.engine.tool_metadata,
772
+ )
773
+ sync_app_state(bundle)
774
+ return True
775
+
776
+
777
+ async def _render_command_result(
778
+ result: CommandResult,
779
+ print_system: SystemPrinter,
780
+ clear_output: ClearHandler,
781
+ render_event: StreamRenderer | None = None,
782
+ ) -> None:
783
+ if result.clear_screen:
784
+ await clear_output()
785
+ if result.replay_messages and render_event is not None:
786
+ # Replay restored conversation messages as transcript events
787
+ from openharness.engine.stream_events import AssistantTextDelta, AssistantTurnComplete
788
+ from openharness.api.usage import UsageSnapshot
789
+
790
+ await clear_output()
791
+ await print_system("Session restored:")
792
+ for msg in result.replay_messages:
793
+ if msg.role == "user":
794
+ await print_system(f"> {msg.text}")
795
+ elif msg.role == "assistant" and msg.text.strip():
796
+ await render_event(AssistantTextDelta(text=msg.text))
797
+ await render_event(AssistantTurnComplete(message=msg, usage=UsageSnapshot()))
798
+ if result.message and not result.replay_messages:
799
+ await print_system(result.message)