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,139 @@
1
+ """File-backed session memory for compact continuity."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from hashlib import sha1
6
+ from pathlib import Path
7
+
8
+ from openharness.config.paths import get_data_dir
9
+ from openharness.engine.messages import ConversationMessage, ToolResultBlock
10
+ from openharness.services.token_estimation import estimate_tokens
11
+ from openharness.utils.fs import atomic_write_text
12
+
13
+ MAX_SESSION_MEMORY_CHARS = 12_000
14
+ MAX_RECENT_LINES = 80
15
+
16
+
17
+ def get_session_memory_dir(cwd: str | Path) -> Path:
18
+ """Return the project session-memory directory."""
19
+
20
+ root = Path(cwd).resolve()
21
+ digest = sha1(str(root).encode("utf-8")).hexdigest()[:12]
22
+ path = get_data_dir() / "session-memory" / f"{root.name}-{digest}"
23
+ path.mkdir(parents=True, exist_ok=True)
24
+ return path
25
+
26
+
27
+ def get_session_memory_path(cwd: str | Path, session_id: str | None = None) -> Path:
28
+ """Return the markdown session-memory path."""
29
+
30
+ safe_session = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in (session_id or "default"))
31
+ return get_session_memory_dir(cwd) / f"{safe_session or 'default'}.md"
32
+
33
+
34
+ def prepare_session_memory_metadata(
35
+ cwd: str | Path,
36
+ tool_metadata: dict[str, object],
37
+ *,
38
+ session_id: str | None = None,
39
+ ) -> Path:
40
+ """Ensure metadata points compaction to the session-memory file."""
41
+
42
+ sid = session_id or str(tool_metadata.get("session_id") or "default")
43
+ path = get_session_memory_path(cwd, sid)
44
+ tool_metadata["session_memory_path"] = str(path)
45
+ return path
46
+
47
+
48
+ def get_session_memory_content(path: str | Path | None) -> str:
49
+ """Read session memory content if available."""
50
+
51
+ if not path:
52
+ return ""
53
+ candidate = Path(path).expanduser()
54
+ if not candidate.exists():
55
+ return ""
56
+ try:
57
+ return candidate.read_text(encoding="utf-8", errors="replace")
58
+ except OSError:
59
+ return ""
60
+
61
+
62
+ def update_session_memory_file(
63
+ cwd: str | Path,
64
+ messages: list[ConversationMessage],
65
+ *,
66
+ tool_metadata: dict[str, object] | None = None,
67
+ session_id: str | None = None,
68
+ ) -> Path:
69
+ """Update the deterministic session-memory checkpoint."""
70
+
71
+ path = prepare_session_memory_metadata(cwd, tool_metadata or {}, session_id=session_id)
72
+ body = build_session_memory_document(messages, tool_metadata=tool_metadata)
73
+ atomic_write_text(path, body)
74
+ return path
75
+
76
+
77
+ def build_session_memory_document(
78
+ messages: list[ConversationMessage],
79
+ *,
80
+ tool_metadata: dict[str, object] | None = None,
81
+ ) -> str:
82
+ """Build a compact markdown checkpoint for the current session."""
83
+
84
+ state = tool_metadata.get("task_focus_state") if isinstance(tool_metadata, dict) else None
85
+ goal = ""
86
+ next_step = ""
87
+ verified: list[str] = []
88
+ artifacts: list[str] = []
89
+ if isinstance(state, dict):
90
+ goal = str(state.get("goal") or "").strip()
91
+ next_step = str(state.get("next_step") or "").strip()
92
+ verified = [str(item).strip() for item in state.get("verified_state", []) if str(item).strip()]
93
+ artifacts = [str(item).strip() for item in state.get("active_artifacts", []) if str(item).strip()]
94
+
95
+ lines = ["# Session Memory", ""]
96
+ lines.extend(["## Current State", goal or "(no current goal recorded)", ""])
97
+ if next_step:
98
+ lines.extend(["## Next Step", next_step, ""])
99
+ if verified:
100
+ lines.extend(["## Verified Work", *[f"- {item}" for item in verified[-10:]], ""])
101
+ if artifacts:
102
+ lines.extend(["## Active Artifacts", *[f"- {item}" for item in artifacts[-10:]], ""])
103
+ lines.extend(["## Recent Conversation", *_recent_message_lines(messages), ""])
104
+ text = "\n".join(lines).strip() + "\n"
105
+ if len(text) > MAX_SESSION_MEMORY_CHARS:
106
+ text = text[:MAX_SESSION_MEMORY_CHARS].rsplit("\n", 1)[0]
107
+ text += "\n\n> Session memory was truncated to stay within budget.\n"
108
+ return text
109
+
110
+
111
+ def session_memory_to_compact_text(content: str) -> str:
112
+ """Prepare persisted session memory for insertion across compact boundaries."""
113
+
114
+ stripped = content.strip()
115
+ if not stripped:
116
+ return ""
117
+ if estimate_tokens(stripped) > 4_000:
118
+ stripped = stripped[:MAX_SESSION_MEMORY_CHARS].rsplit("\n", 1)[0]
119
+ return "Session memory checkpoint from earlier in this conversation:\n" + stripped
120
+
121
+
122
+ def _recent_message_lines(messages: list[ConversationMessage]) -> list[str]:
123
+ lines: list[str] = []
124
+ for message in messages[-MAX_RECENT_LINES:]:
125
+ line = _summarize_message(message)
126
+ if line:
127
+ lines.append(f"- {line}")
128
+ return lines or ["- (no recent messages)"]
129
+
130
+
131
+ def _summarize_message(message: ConversationMessage) -> str:
132
+ text = " ".join(message.text.split())
133
+ if text:
134
+ return f"{message.role}: {text[:220]}"
135
+ if message.tool_uses:
136
+ return f"{message.role}: tool calls -> {', '.join(block.name for block in message.tool_uses[:6])}"
137
+ if any(isinstance(block, ToolResultBlock) for block in message.content):
138
+ return f"{message.role}: tool results returned"
139
+ return f"{message.role}: [non-text content]"
@@ -0,0 +1,230 @@
1
+ """Session persistence helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from hashlib import sha1
8
+ from pathlib import Path
9
+ from typing import Any
10
+ from uuid import uuid4
11
+
12
+ from openharness.api.usage import UsageSnapshot
13
+ from openharness.config.paths import get_sessions_dir
14
+ from openharness.engine.messages import ConversationMessage, sanitize_conversation_messages
15
+ from openharness.utils.fs import atomic_write_text
16
+
17
+
18
+ _PERSISTED_TOOL_METADATA_KEYS = (
19
+ "permission_mode",
20
+ "read_file_state",
21
+ "invoked_skills",
22
+ "async_agent_state",
23
+ "async_agent_tasks",
24
+ "recent_work_log",
25
+ "recent_verified_work",
26
+ "task_focus_state",
27
+ "compact_checkpoints",
28
+ "compact_last",
29
+ )
30
+
31
+
32
+ def _sanitize_metadata(value: Any) -> Any:
33
+ if isinstance(value, (str, int, float, bool)) or value is None:
34
+ return value
35
+ if isinstance(value, Path):
36
+ return str(value)
37
+ if isinstance(value, dict):
38
+ return {str(key): _sanitize_metadata(item) for key, item in value.items()}
39
+ if isinstance(value, (list, tuple, set)):
40
+ return [_sanitize_metadata(item) for item in value]
41
+ return str(value)
42
+
43
+
44
+ def _persistable_tool_metadata(tool_metadata: dict[str, object] | None) -> dict[str, Any]:
45
+ if not isinstance(tool_metadata, dict):
46
+ return {}
47
+ payload: dict[str, Any] = {}
48
+ for key in _PERSISTED_TOOL_METADATA_KEYS:
49
+ if key in tool_metadata:
50
+ payload[key] = _sanitize_metadata(tool_metadata[key])
51
+ return payload
52
+
53
+
54
+ def get_project_session_dir(cwd: str | Path) -> Path:
55
+ """Return the session directory for a project."""
56
+ path = Path(cwd).resolve()
57
+ digest = sha1(str(path).encode("utf-8")).hexdigest()[:12]
58
+ session_dir = get_sessions_dir() / f"{path.name}-{digest}"
59
+ session_dir.mkdir(parents=True, exist_ok=True)
60
+ return session_dir
61
+
62
+
63
+ def save_session_snapshot(
64
+ *,
65
+ cwd: str | Path,
66
+ model: str,
67
+ system_prompt: str,
68
+ messages: list[ConversationMessage],
69
+ usage: UsageSnapshot,
70
+ session_id: str | None = None,
71
+ tool_metadata: dict[str, object] | None = None,
72
+ ) -> Path:
73
+ """Persist a session snapshot. Saves both by ID and as latest."""
74
+ session_dir = get_project_session_dir(cwd)
75
+ sid = session_id or uuid4().hex[:12]
76
+ now = time.time()
77
+ messages = sanitize_conversation_messages(messages)
78
+ # Extract a summary from the first user message
79
+ summary = ""
80
+ for msg in messages:
81
+ if msg.role == "user" and msg.text.strip():
82
+ summary = msg.text.strip()[:80]
83
+ break
84
+
85
+ payload = {
86
+ "session_id": sid,
87
+ "cwd": str(Path(cwd).resolve()),
88
+ "model": model,
89
+ "system_prompt": system_prompt,
90
+ "messages": [message.model_dump(mode="json") for message in messages],
91
+ "usage": usage.model_dump(),
92
+ "tool_metadata": _persistable_tool_metadata(tool_metadata),
93
+ "created_at": now,
94
+ "summary": summary,
95
+ "message_count": len(messages),
96
+ }
97
+ data = json.dumps(payload, indent=2) + "\n"
98
+
99
+ # Save as latest
100
+ latest_path = session_dir / "latest.json"
101
+ atomic_write_text(latest_path, data)
102
+
103
+ # Save by session ID
104
+ session_path = session_dir / f"session-{sid}.json"
105
+ atomic_write_text(session_path, data)
106
+
107
+ return latest_path
108
+
109
+
110
+ def _sanitize_snapshot_payload(payload: dict[str, Any]) -> dict[str, Any]:
111
+ """Normalize persisted messages for forward compatibility."""
112
+ raw_messages = payload.get("messages", [])
113
+ if isinstance(raw_messages, list):
114
+ messages = sanitize_conversation_messages(
115
+ [ConversationMessage.model_validate(item) for item in raw_messages]
116
+ )
117
+ payload = dict(payload)
118
+ payload["messages"] = [message.model_dump(mode="json") for message in messages]
119
+ payload["message_count"] = len(messages)
120
+ return payload
121
+
122
+
123
+ def load_session_snapshot(cwd: str | Path) -> dict[str, Any] | None:
124
+ """Load the most recent session snapshot for the project."""
125
+ path = get_project_session_dir(cwd) / "latest.json"
126
+ if not path.exists():
127
+ return None
128
+ return _sanitize_snapshot_payload(json.loads(path.read_text(encoding="utf-8")))
129
+
130
+
131
+ def list_session_snapshots(cwd: str | Path, limit: int = 20) -> list[dict[str, Any]]:
132
+ """List saved sessions for the project, newest first."""
133
+ session_dir = get_project_session_dir(cwd)
134
+ sessions: list[dict[str, Any]] = []
135
+ seen_ids: set[str] = set()
136
+
137
+ # Named session files
138
+ for path in sorted(session_dir.glob("session-*.json"), key=lambda p: p.stat().st_mtime, reverse=True):
139
+ try:
140
+ data = json.loads(path.read_text(encoding="utf-8"))
141
+ sid = data.get("session_id", path.stem.replace("session-", ""))
142
+ seen_ids.add(sid)
143
+ summary = data.get("summary", "")
144
+ if not summary:
145
+ # Extract from first user message
146
+ for msg in data.get("messages", []):
147
+ if msg.get("role") == "user":
148
+ texts = [b.get("text", "") for b in msg.get("content", []) if b.get("type") == "text"]
149
+ summary = " ".join(texts).strip()[:80]
150
+ if summary:
151
+ break
152
+ sessions.append({
153
+ "session_id": sid,
154
+ "summary": summary,
155
+ "message_count": data.get("message_count", len(data.get("messages", []))),
156
+ "model": data.get("model", ""),
157
+ "created_at": data.get("created_at", path.stat().st_mtime),
158
+ })
159
+ except (json.JSONDecodeError, OSError):
160
+ continue
161
+ if len(sessions) >= limit:
162
+ break
163
+
164
+ # Also include latest.json if it has no corresponding session file
165
+ latest_path = session_dir / "latest.json"
166
+ if latest_path.exists() and len(sessions) < limit:
167
+ try:
168
+ data = json.loads(latest_path.read_text(encoding="utf-8"))
169
+ sid = data.get("session_id", "latest")
170
+ if sid not in seen_ids:
171
+ summary = data.get("summary", "")
172
+ if not summary:
173
+ for msg in data.get("messages", []):
174
+ if msg.get("role") == "user":
175
+ texts = [b.get("text", "") for b in msg.get("content", []) if b.get("type") == "text"]
176
+ summary = " ".join(texts).strip()[:80]
177
+ if summary:
178
+ break
179
+ sessions.append({
180
+ "session_id": sid,
181
+ "summary": summary or "(latest session)",
182
+ "message_count": data.get("message_count", len(data.get("messages", []))),
183
+ "model": data.get("model", ""),
184
+ "created_at": data.get("created_at", latest_path.stat().st_mtime),
185
+ })
186
+ except (json.JSONDecodeError, OSError):
187
+ pass
188
+
189
+ # Sort by created_at descending
190
+ sessions.sort(key=lambda s: s.get("created_at", 0), reverse=True)
191
+ return sessions[:limit]
192
+
193
+
194
+ def load_session_by_id(cwd: str | Path, session_id: str) -> dict[str, Any] | None:
195
+ """Load a specific session by ID."""
196
+ session_dir = get_project_session_dir(cwd)
197
+ # Try named session first
198
+ path = session_dir / f"session-{session_id}.json"
199
+ if path.exists():
200
+ return _sanitize_snapshot_payload(json.loads(path.read_text(encoding="utf-8")))
201
+ # Fallback to latest.json if session_id matches
202
+ latest = session_dir / "latest.json"
203
+ if latest.exists():
204
+ data = _sanitize_snapshot_payload(json.loads(latest.read_text(encoding="utf-8")))
205
+ if data.get("session_id") == session_id or session_id == "latest":
206
+ return data
207
+ return None
208
+
209
+
210
+ def export_session_markdown(
211
+ *,
212
+ cwd: str | Path,
213
+ messages: list[ConversationMessage],
214
+ ) -> Path:
215
+ """Export the session transcript as Markdown."""
216
+ session_dir = get_project_session_dir(cwd)
217
+ path = session_dir / "transcript.md"
218
+ parts: list[str] = ["# OpenHarness Session Transcript"]
219
+ for message in messages:
220
+ parts.append(f"\n## {message.role.capitalize()}\n")
221
+ text = message.text.strip()
222
+ if text:
223
+ parts.append(text)
224
+ for block in message.tool_uses:
225
+ parts.append(f"\n```tool\n{block.name} {json.dumps(block.input, ensure_ascii=True)}\n```")
226
+ for block in message.content:
227
+ if getattr(block, "type", "") == "tool_result":
228
+ parts.append(f"\n```tool-result\n{block.content}\n```")
229
+ atomic_write_text(path, "\n".join(parts).strip() + "\n")
230
+ return path
@@ -0,0 +1,15 @@
1
+ """Simple token estimation utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def estimate_tokens(text: str) -> int:
7
+ """Estimate tokens from plain text using a rough character heuristic."""
8
+ if not text:
9
+ return 0
10
+ return max(1, (len(text) + 3) // 4)
11
+
12
+
13
+ def estimate_message_tokens(messages: list[str]) -> int:
14
+ """Estimate tokens for a collection of message strings."""
15
+ return sum(estimate_tokens(message) for message in messages)
@@ -0,0 +1,55 @@
1
+ """Tool-output context budget helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+ DEFAULT_TOOL_OUTPUT_INLINE_CHARS = 16_000
11
+ DEFAULT_TOOL_OUTPUT_PREVIEW_CHARS = 3_000
12
+ DEFAULT_MICROCOMPACT_TOOL_RESULT_CHARS = 4_000
13
+
14
+
15
+ def _read_positive_int_env(name: str, default: int, *, minimum: int = 1) -> int:
16
+ raw = os.environ.get(name, "").strip()
17
+ if not raw:
18
+ return default
19
+ try:
20
+ return max(minimum, int(raw))
21
+ except ValueError:
22
+ log.warning("Ignoring invalid %s=%r", name, raw)
23
+ return default
24
+
25
+
26
+ def tool_output_inline_chars() -> int:
27
+ return _read_positive_int_env(
28
+ "OPENHARNESS_TOOL_OUTPUT_INLINE_CHARS",
29
+ DEFAULT_TOOL_OUTPUT_INLINE_CHARS,
30
+ minimum=256,
31
+ )
32
+
33
+
34
+ def tool_output_preview_chars() -> int:
35
+ return _read_positive_int_env(
36
+ "OPENHARNESS_TOOL_OUTPUT_PREVIEW_CHARS",
37
+ DEFAULT_TOOL_OUTPUT_PREVIEW_CHARS,
38
+ minimum=128,
39
+ )
40
+
41
+
42
+ def microcompact_tool_result_chars() -> int:
43
+ return _read_positive_int_env(
44
+ "OPENHARNESS_MICROCOMPACT_TOOL_RESULT_CHARS",
45
+ DEFAULT_MICROCOMPACT_TOOL_RESULT_CHARS,
46
+ minimum=256,
47
+ )
48
+
49
+
50
+ def is_microcompactable_tool_result(tool_name: str, content: str) -> bool:
51
+ """Return True when a tool result should be eligible for old-result clearing."""
52
+ normalized = tool_name.strip()
53
+ if normalized.startswith("mcp__"):
54
+ return True
55
+ return len(content) >= microcompact_tool_result_chars()
@@ -0,0 +1,44 @@
1
+ """Skill exports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING: # pragma: no cover
8
+ from openharness.skills.registry import SkillRegistry
9
+ from openharness.skills.types import SkillDefinition
10
+
11
+ __all__ = [
12
+ "SkillDefinition",
13
+ "SkillRegistry",
14
+ "discover_project_skill_dirs",
15
+ "get_user_skill_dirs",
16
+ "get_user_skills_dir",
17
+ "load_skill_registry",
18
+ ]
19
+
20
+
21
+ def __getattr__(name: str):
22
+ if name in {"discover_project_skill_dirs", "get_user_skill_dirs", "get_user_skills_dir", "load_skill_registry"}:
23
+ from openharness.skills.loader import (
24
+ discover_project_skill_dirs,
25
+ get_user_skill_dirs,
26
+ get_user_skills_dir,
27
+ load_skill_registry,
28
+ )
29
+
30
+ return {
31
+ "discover_project_skill_dirs": discover_project_skill_dirs,
32
+ "get_user_skill_dirs": get_user_skill_dirs,
33
+ "get_user_skills_dir": get_user_skills_dir,
34
+ "load_skill_registry": load_skill_registry,
35
+ }[name]
36
+ if name == "SkillRegistry":
37
+ from openharness.skills.registry import SkillRegistry
38
+
39
+ return SkillRegistry
40
+ if name == "SkillDefinition":
41
+ from openharness.skills.types import SkillDefinition
42
+
43
+ return SkillDefinition
44
+ raise AttributeError(name)
@@ -0,0 +1,97 @@
1
+ """Shared YAML frontmatter parsing for SKILL.md files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ import yaml
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def parse_bool_frontmatter(value: Any, *, default: bool) -> bool:
14
+ """Parse permissive YAML frontmatter booleans."""
15
+ if value is None:
16
+ return default
17
+ if isinstance(value, bool):
18
+ return value
19
+ normalized = str(value).strip().lower()
20
+ if normalized in {"1", "true", "yes", "on"}:
21
+ return True
22
+ if normalized in {"0", "false", "no", "off"}:
23
+ return False
24
+ return default
25
+
26
+
27
+ def optional_frontmatter_str(value: Any) -> str | None:
28
+ """Return a stripped string value, or ``None`` when absent/blank."""
29
+ if isinstance(value, str) and value.strip():
30
+ return value.strip()
31
+ return None
32
+
33
+
34
+ def parse_skill_metadata(
35
+ default_name: str,
36
+ content: str,
37
+ *,
38
+ fallback_template: str = "Skill: {name}",
39
+ ) -> dict[str, Any]:
40
+ """Extract metadata from a SKILL.md file.
41
+
42
+ Parses YAML frontmatter (``---`` delimited) via ``yaml.safe_load`` so that
43
+ folded block scalars (``>``), literal block scalars (``|``), quoted values,
44
+ and other standard YAML constructs are handled correctly. Falls back to
45
+ ``# heading`` + first body paragraph when no usable frontmatter is present,
46
+ and finally to ``fallback_template`` when no description can be derived.
47
+ """
48
+ name = default_name
49
+ description = ""
50
+ frontmatter: dict[str, Any] = {}
51
+ lines = content.splitlines()
52
+
53
+ if content.startswith("---\n"):
54
+ end_index = content.find("\n---\n", 4)
55
+ if end_index != -1:
56
+ try:
57
+ metadata = yaml.safe_load(content[4:end_index])
58
+ if isinstance(metadata, dict):
59
+ frontmatter = metadata
60
+ val = metadata.get("name")
61
+ if isinstance(val, str) and val.strip():
62
+ name = val.strip()
63
+ val = metadata.get("description")
64
+ if isinstance(val, str) and val.strip():
65
+ description = val.strip()
66
+ except yaml.YAMLError:
67
+ logger.debug("Failed to parse YAML frontmatter for skill %s", default_name)
68
+
69
+ if not description:
70
+ for line in lines:
71
+ stripped = line.strip()
72
+ if stripped.startswith("# "):
73
+ if not name or name == default_name:
74
+ name = stripped[2:].strip() or default_name
75
+ continue
76
+ if stripped and not stripped.startswith("---") and not stripped.startswith("#"):
77
+ description = stripped[:200]
78
+ break
79
+
80
+ if not description:
81
+ description = fallback_template.format(name=name)
82
+ return {"name": name, "description": description, "frontmatter": frontmatter}
83
+
84
+
85
+ def parse_skill_frontmatter(
86
+ default_name: str,
87
+ content: str,
88
+ *,
89
+ fallback_template: str = "Skill: {name}",
90
+ ) -> tuple[str, str]:
91
+ """Extract ``name`` and ``description`` from a SKILL.md file."""
92
+ metadata = parse_skill_metadata(
93
+ default_name,
94
+ content,
95
+ fallback_template=fallback_template,
96
+ )
97
+ return str(metadata["name"]), str(metadata["description"])
@@ -0,0 +1,75 @@
1
+ """Bundled skill definitions loaded from .md files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from openharness.skills._frontmatter import (
8
+ optional_frontmatter_str,
9
+ parse_bool_frontmatter,
10
+ parse_skill_frontmatter,
11
+ parse_skill_metadata,
12
+ )
13
+ from openharness.skills.types import SkillDefinition
14
+
15
+ _CONTENT_DIR = Path(__file__).parent / "content"
16
+
17
+
18
+ def get_bundled_skills() -> list[SkillDefinition]:
19
+ """Load all bundled skills from the content/ directory."""
20
+ skills: list[SkillDefinition] = []
21
+ if not _CONTENT_DIR.exists():
22
+ return skills
23
+ for path in sorted(_CONTENT_DIR.glob("*.md")):
24
+ content = path.read_text(encoding="utf-8")
25
+ metadata = _parse_metadata(path.stem, content)
26
+ display_name = metadata["name"] if metadata["name"] != path.stem else None
27
+ skills.append(
28
+ SkillDefinition(
29
+ name=metadata["name"],
30
+ description=metadata["description"],
31
+ content=content,
32
+ source="bundled",
33
+ path=str(path),
34
+ base_dir=str(path.parent),
35
+ command_name=path.stem,
36
+ display_name=display_name,
37
+ user_invocable=metadata["user_invocable"],
38
+ disable_model_invocation=metadata["disable_model_invocation"],
39
+ model=metadata["model"],
40
+ argument_hint=metadata["argument_hint"],
41
+ )
42
+ )
43
+ return skills
44
+
45
+
46
+ def _parse_frontmatter(default_name: str, content: str) -> tuple[str, str]:
47
+ """Extract name and description from a bundled skill markdown file.
48
+
49
+ Delegates to the shared parser so YAML block scalars (``>``, ``|``),
50
+ quoted values, and other standard YAML constructs are handled the same
51
+ way as user-installed skills.
52
+ """
53
+ return parse_skill_frontmatter(
54
+ default_name,
55
+ content,
56
+ fallback_template="Bundled skill: {name}",
57
+ )
58
+
59
+
60
+ def _parse_metadata(default_name: str, content: str) -> dict:
61
+ parsed = parse_skill_metadata(default_name, content, fallback_template="Bundled skill: {name}")
62
+ frontmatter = parsed.get("frontmatter")
63
+ if not isinstance(frontmatter, dict):
64
+ frontmatter = {}
65
+ return {
66
+ "name": str(parsed["name"]),
67
+ "description": str(parsed["description"]),
68
+ "user_invocable": parse_bool_frontmatter(frontmatter.get("user-invocable"), default=True),
69
+ "disable_model_invocation": parse_bool_frontmatter(
70
+ frontmatter.get("disable-model-invocation"),
71
+ default=False,
72
+ ),
73
+ "model": optional_frontmatter_str(frontmatter.get("model")),
74
+ "argument_hint": optional_frontmatter_str(frontmatter.get("argument-hint")),
75
+ }
@@ -0,0 +1,25 @@
1
+ # commit
2
+
3
+ Create clean, well-structured git commits.
4
+
5
+ ## When to use
6
+
7
+ Use when the user asks to commit changes, create a PR, or prepare code for review.
8
+
9
+ ## Workflow
10
+
11
+ 1. Run `git status` and `git diff` to understand all changes
12
+ 2. Analyze changes: categorize as feature, fix, refactor, docs, test, etc.
13
+ 3. Draft a concise commit message:
14
+ - First line: imperative mood, under 72 chars, describes the "why"
15
+ - Body (if needed): explain context, trade-offs, or breaking changes
16
+ 4. Stage only relevant files — never stage .env, credentials, or large binaries
17
+ 5. Create the commit
18
+
19
+ ## Rules
20
+
21
+ - Prefer specific `git add <file>` over `git add -A`
22
+ - Never use `--no-verify` unless explicitly asked
23
+ - Never amend published commits unless explicitly asked
24
+ - If a pre-commit hook fails, fix the issue and create a NEW commit (don't --amend)
25
+ - Include `Co-Authored-By` if pair programming