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.
- qwen_sci-0.2.2.dist-info/METADATA +492 -0
- qwen_sci-0.2.2.dist-info/RECORD +549 -0
- qwen_sci-0.2.2.dist-info/WHEEL +4 -0
- qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
- qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
- src/1.txt +1 -0
- src/__init__.py +1 -0
- src/__main__.py +5 -0
- src/agents/__init__.py +1 -0
- src/agents/experiment_design_agent/__init__.py +268 -0
- src/agents/experiment_design_agent/artifacts.py +747 -0
- src/agents/experiment_design_agent/cache.py +319 -0
- src/agents/experiment_design_agent/completeness.py +130 -0
- src/agents/experiment_design_agent/contracts.py +771 -0
- src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
- src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
- src/agents/experiment_design_agent/discipline_catalog.py +178 -0
- src/agents/experiment_design_agent/evidence_cards.py +338 -0
- src/agents/experiment_design_agent/evidence_planner.py +452 -0
- src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
- src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
- src/agents/experiment_design_agent/idea_adapter.py +100 -0
- src/agents/experiment_design_agent/idea_intake.py +87 -0
- src/agents/experiment_design_agent/llm_json.py +328 -0
- src/agents/experiment_design_agent/orchestrator.py +1220 -0
- src/agents/experiment_design_agent/reasoning_context.py +183 -0
- src/agents/experiment_design_agent/reasoning_validation.py +546 -0
- src/agents/experiment_design_agent/run.py +481 -0
- src/agents/experiment_design_agent/run_logging.py +292 -0
- src/agents/experiment_design_agent/scope_gate.py +198 -0
- src/agents/experiment_design_agent/study_type_composer.py +1360 -0
- src/agents/experiment_design_agent/survey_evidence.py +1381 -0
- src/agents/experiment_design_agent/template_router.py +195 -0
- src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
- src/agents/idea_agent/__init__.py +1 -0
- src/agents/idea_agent/agent/__init__.py +6 -0
- src/agents/idea_agent/agent/artifacts.py +308 -0
- src/agents/idea_agent/agent/base.py +122 -0
- src/agents/idea_agent/agent/ligagent.py +488 -0
- src/agents/idea_agent/agent/mcts.py +2060 -0
- src/agents/idea_agent/agent/prompts/__init__.py +48 -0
- src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
- src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
- src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
- src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
- src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
- src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
- src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
- src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
- src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
- src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
- src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
- src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
- src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
- src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
- src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
- src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
- src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
- src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
- src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
- src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
- src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
- src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
- src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
- src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
- src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
- src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
- src/agents/idea_agent/example/idea_result.json +900 -0
- src/agents/idea_agent/run.py +276 -0
- src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
- src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
- src/agents/idea_agent/tests/test_idea_debate.py +480 -0
- src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
- src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
- src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
- src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
- src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
- src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
- src/agents/idea_agent/tests/test_result_persistence.py +183 -0
- src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
- src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
- src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
- src/agents/idea_agent/utils/core/__init__.py +1 -0
- src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
- src/agents/idea_agent/utils/core/chat_errors.py +95 -0
- src/agents/idea_agent/utils/core/chat_router.py +130 -0
- src/agents/idea_agent/utils/core/chat_transport.py +188 -0
- src/agents/idea_agent/utils/core/config_loader.py +166 -0
- src/agents/idea_agent/utils/core/json_utils.py +28 -0
- src/agents/idea_agent/utils/core/logger.py +287 -0
- src/agents/idea_agent/utils/core/progress.py +58 -0
- src/agents/idea_agent/utils/core/response_parsing.py +57 -0
- src/agents/idea_agent/utils/core/run_inputs.py +198 -0
- src/agents/idea_agent/utils/mcts/__init__.py +1 -0
- src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
- src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
- src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
- src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
- src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
- src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
- src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
- src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
- src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
- src/agents/idea_agent/utils/papers/__init__.py +1 -0
- src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
- src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
- src/agents/idea_agent/utils/prompting/__init__.py +1 -0
- src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
- src/agents/idea_agent/utils/workflow/__init__.py +1 -0
- src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
- src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
- src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
- src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
- src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
- src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
- src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
- src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
- src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
- src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
- src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
- src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
- src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
- src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
- src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
- src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
- src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
- src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
- src/agents/research_plan_author/__init__.py +50 -0
- src/agents/research_plan_author/artifacts.py +270 -0
- src/agents/research_plan_author/authoring_blueprint.py +1246 -0
- src/agents/research_plan_author/bibtex_renderer.py +252 -0
- src/agents/research_plan_author/contract_repair.py +131 -0
- src/agents/research_plan_author/contracts.py +745 -0
- src/agents/research_plan_author/cross_section_editor.py +232 -0
- src/agents/research_plan_author/document_quality.py +987 -0
- src/agents/research_plan_author/idea_evolution.py +223 -0
- src/agents/research_plan_author/input_loader.py +76 -0
- src/agents/research_plan_author/latex_compiler.py +260 -0
- src/agents/research_plan_author/latex_safety.py +159 -0
- src/agents/research_plan_author/llm_json.py +74 -0
- src/agents/research_plan_author/markdown_renderer.py +275 -0
- src/agents/research_plan_author/pdf_validator.py +149 -0
- src/agents/research_plan_author/render.py +166 -0
- src/agents/research_plan_author/render_artifacts.py +187 -0
- src/agents/research_plan_author/run.py +765 -0
- src/agents/research_plan_author/run_logging.py +209 -0
- src/agents/research_plan_author/section_cache.py +184 -0
- src/agents/research_plan_author/section_composer.py +1764 -0
- src/agents/research_plan_author/section_router.py +209 -0
- src/agents/research_plan_author/semantic_validator.py +254 -0
- src/agents/research_plan_author/source_bundle.py +46 -0
- src/agents/research_plan_author/source_registry.py +208 -0
- src/agents/research_plan_author/survey_source_loader.py +137 -0
- src/agents/research_plan_author/template_adapter.py +173 -0
- src/agents/research_plan_author/template_profile.py +204 -0
- src/agents/research_plan_author/tex_renderer.py +496 -0
- src/agents/research_plan_author/theory_presentation.py +134 -0
- src/agents/research_plan_author/theory_spine.py +703 -0
- src/agents/survey_agent/Dockerfile +54 -0
- src/agents/survey_agent/README.md +350 -0
- src/agents/survey_agent/config/deep_survey.yaml +217 -0
- src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
- src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
- src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
- src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
- src/agents/survey_agent/env_report.md +348 -0
- src/agents/survey_agent/modules/__init__.py +0 -0
- src/agents/survey_agent/modules/code_collector.py +2194 -0
- src/agents/survey_agent/modules/code_report_generator.py +1282 -0
- src/agents/survey_agent/modules/data_manager.py +1713 -0
- src/agents/survey_agent/modules/database.py +167 -0
- src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
- src/agents/survey_agent/modules/fulltext_http.py +134 -0
- src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
- src/agents/survey_agent/modules/judge.py +434 -0
- src/agents/survey_agent/modules/outcome_RAG.py +236 -0
- src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
- src/agents/survey_agent/modules/pe.py +1729 -0
- src/agents/survey_agent/modules/pseudo_creator.py +782 -0
- src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
- src/agents/survey_agent/modules/refine_agent.py +1952 -0
- src/agents/survey_agent/modules/survey_generator.py +5691 -0
- src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
- src/agents/survey_agent/modules/visual_prompts.py +327 -0
- src/agents/survey_agent/modules/work_analyzer.py +2750 -0
- src/agents/survey_agent/modules/work_collector.py +5649 -0
- src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
- src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
- src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
- src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
- src/agents/survey_agent/utils/__init__.py +0 -0
- src/agents/survey_agent/utils/ablation_utils.py +317 -0
- src/agents/survey_agent/utils/api_call.py +2518 -0
- src/agents/survey_agent/utils/chat_utils.py +82 -0
- src/agents/survey_agent/utils/config_utils.py +70 -0
- src/agents/survey_agent/utils/convert_to_md.py +29 -0
- src/agents/survey_agent/utils/err_info.py +24 -0
- src/agents/survey_agent/utils/file_utils.py +176 -0
- src/agents/survey_agent/utils/gpu_utils.py +145 -0
- src/agents/survey_agent/utils/graph_visualizer.py +279 -0
- src/agents/survey_agent/utils/html_utils.py +181 -0
- src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
- src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
- src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
- src/agents/survey_agent/utils/mineru_utils.py +396 -0
- src/agents/survey_agent/utils/paper.py +144 -0
- src/agents/survey_agent/utils/read_yaml.py +15 -0
- src/agents/survey_agent/utils/repo_utils.py +167 -0
- src/agents/survey_agent/utils/rich_logger.py +77 -0
- src/agents/survey_agent/utils/step2v2.py +874 -0
- src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
- src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
- src/agents/survey_agent/utils/utils.py +118 -0
- src/agents/survey_agent/utils/work.py +2 -0
- src/cli.py +1611 -0
- src/config/__init__.py +213 -0
- src/config/default.yaml +1122 -0
- src/harness/LICENSE +21 -0
- src/harness/VENDORED.md +14 -0
- src/harness/src/__init__.py +0 -0
- src/harness/src/openharness/__init__.py +0 -0
- src/harness/src/openharness/__main__.py +6 -0
- src/harness/src/openharness/api/__init__.py +21 -0
- src/harness/src/openharness/api/client.py +281 -0
- src/harness/src/openharness/api/codex_client.py +407 -0
- src/harness/src/openharness/api/copilot_auth.py +240 -0
- src/harness/src/openharness/api/copilot_client.py +134 -0
- src/harness/src/openharness/api/errors.py +19 -0
- src/harness/src/openharness/api/openai_client.py +577 -0
- src/harness/src/openharness/api/provider.py +186 -0
- src/harness/src/openharness/api/registry.py +437 -0
- src/harness/src/openharness/api/usage.py +17 -0
- src/harness/src/openharness/auth/__init__.py +29 -0
- src/harness/src/openharness/auth/external.py +610 -0
- src/harness/src/openharness/auth/flows.py +189 -0
- src/harness/src/openharness/auth/manager.py +482 -0
- src/harness/src/openharness/auth/storage.py +269 -0
- src/harness/src/openharness/autopilot/__init__.py +23 -0
- src/harness/src/openharness/autopilot/service.py +2239 -0
- src/harness/src/openharness/autopilot/types.py +91 -0
- src/harness/src/openharness/bridge/__init__.py +20 -0
- src/harness/src/openharness/bridge/manager.py +106 -0
- src/harness/src/openharness/bridge/session_runner.py +46 -0
- src/harness/src/openharness/bridge/types.py +37 -0
- src/harness/src/openharness/bridge/work_secret.py +41 -0
- src/harness/src/openharness/channels/UPSTREAM +6 -0
- src/harness/src/openharness/channels/__init__.py +22 -0
- src/harness/src/openharness/channels/adapter.py +131 -0
- src/harness/src/openharness/channels/bus/__init__.py +6 -0
- src/harness/src/openharness/channels/bus/events.py +38 -0
- src/harness/src/openharness/channels/bus/queue.py +44 -0
- src/harness/src/openharness/channels/impl/__init__.py +6 -0
- src/harness/src/openharness/channels/impl/base.py +141 -0
- src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
- src/harness/src/openharness/channels/impl/discord.py +311 -0
- src/harness/src/openharness/channels/impl/email.py +410 -0
- src/harness/src/openharness/channels/impl/feishu.py +1342 -0
- src/harness/src/openharness/channels/impl/manager.py +257 -0
- src/harness/src/openharness/channels/impl/matrix.py +700 -0
- src/harness/src/openharness/channels/impl/mochat.py +897 -0
- src/harness/src/openharness/channels/impl/qq.py +141 -0
- src/harness/src/openharness/channels/impl/slack.py +284 -0
- src/harness/src/openharness/channels/impl/telegram.py +525 -0
- src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
- src/harness/src/openharness/cli.py +2551 -0
- src/harness/src/openharness/commands/__init__.py +21 -0
- src/harness/src/openharness/commands/registry.py +2783 -0
- src/harness/src/openharness/config/__init__.py +34 -0
- src/harness/src/openharness/config/paths.py +160 -0
- src/harness/src/openharness/config/schema.py +119 -0
- src/harness/src/openharness/config/settings.py +1104 -0
- src/harness/src/openharness/coordinator/__init__.py +12 -0
- src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
- src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
- src/harness/src/openharness/engine/__init__.py +80 -0
- src/harness/src/openharness/engine/cost_tracker.py +24 -0
- src/harness/src/openharness/engine/messages.py +222 -0
- src/harness/src/openharness/engine/query.py +1188 -0
- src/harness/src/openharness/engine/query_engine.py +313 -0
- src/harness/src/openharness/engine/stream_events.py +90 -0
- src/harness/src/openharness/hooks/__init__.py +50 -0
- src/harness/src/openharness/hooks/events.py +20 -0
- src/harness/src/openharness/hooks/executor.py +242 -0
- src/harness/src/openharness/hooks/hot_reload.py +31 -0
- src/harness/src/openharness/hooks/loader.py +68 -0
- src/harness/src/openharness/hooks/schemas.py +66 -0
- src/harness/src/openharness/hooks/types.py +38 -0
- src/harness/src/openharness/keybindings/__init__.py +14 -0
- src/harness/src/openharness/keybindings/default_bindings.py +11 -0
- src/harness/src/openharness/keybindings/loader.py +22 -0
- src/harness/src/openharness/keybindings/parser.py +18 -0
- src/harness/src/openharness/keybindings/resolver.py +13 -0
- src/harness/src/openharness/mcp/__init__.py +79 -0
- src/harness/src/openharness/mcp/client.py +298 -0
- src/harness/src/openharness/mcp/config.py +16 -0
- src/harness/src/openharness/mcp/types.py +76 -0
- src/harness/src/openharness/memory/__init__.py +25 -0
- src/harness/src/openharness/memory/agent.py +91 -0
- src/harness/src/openharness/memory/manager.py +183 -0
- src/harness/src/openharness/memory/memdir.py +52 -0
- src/harness/src/openharness/memory/migrate.py +137 -0
- src/harness/src/openharness/memory/paths.py +22 -0
- src/harness/src/openharness/memory/relevance.py +145 -0
- src/harness/src/openharness/memory/scan.py +116 -0
- src/harness/src/openharness/memory/schema.py +443 -0
- src/harness/src/openharness/memory/search.py +71 -0
- src/harness/src/openharness/memory/team.py +90 -0
- src/harness/src/openharness/memory/types.py +33 -0
- src/harness/src/openharness/memory/usage.py +153 -0
- src/harness/src/openharness/output_styles/__init__.py +5 -0
- src/harness/src/openharness/output_styles/loader.py +42 -0
- src/harness/src/openharness/permissions/__init__.py +26 -0
- src/harness/src/openharness/permissions/checker.py +200 -0
- src/harness/src/openharness/permissions/modes.py +13 -0
- src/harness/src/openharness/personalization/__init__.py +6 -0
- src/harness/src/openharness/personalization/extractor.py +138 -0
- src/harness/src/openharness/personalization/rules.py +64 -0
- src/harness/src/openharness/personalization/session_hook.py +65 -0
- src/harness/src/openharness/platforms.py +86 -0
- src/harness/src/openharness/plugins/__init__.py +53 -0
- src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
- src/harness/src/openharness/plugins/installer.py +39 -0
- src/harness/src/openharness/plugins/loader.py +730 -0
- src/harness/src/openharness/plugins/schemas.py +24 -0
- src/harness/src/openharness/plugins/types.py +59 -0
- src/harness/src/openharness/prompts/__init__.py +14 -0
- src/harness/src/openharness/prompts/claudemd.py +48 -0
- src/harness/src/openharness/prompts/context.py +187 -0
- src/harness/src/openharness/prompts/environment.py +135 -0
- src/harness/src/openharness/prompts/system_prompt.py +109 -0
- src/harness/src/openharness/sandbox/Dockerfile +6 -0
- src/harness/src/openharness/sandbox/__init__.py +33 -0
- src/harness/src/openharness/sandbox/adapter.py +148 -0
- src/harness/src/openharness/sandbox/docker_backend.py +232 -0
- src/harness/src/openharness/sandbox/docker_image.py +103 -0
- src/harness/src/openharness/sandbox/path_validator.py +37 -0
- src/harness/src/openharness/sandbox/session.py +63 -0
- src/harness/src/openharness/services/__init__.py +30 -0
- src/harness/src/openharness/services/autodream/__init__.py +34 -0
- src/harness/src/openharness/services/autodream/backup.py +104 -0
- src/harness/src/openharness/services/autodream/lock.py +138 -0
- src/harness/src/openharness/services/autodream/prompt.py +128 -0
- src/harness/src/openharness/services/autodream/service.py +313 -0
- src/harness/src/openharness/services/compact/__init__.py +1725 -0
- src/harness/src/openharness/services/cron.py +137 -0
- src/harness/src/openharness/services/cron_scheduler.py +595 -0
- src/harness/src/openharness/services/lsp/__init__.py +216 -0
- src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
- src/harness/src/openharness/services/oauth/__init__.py +0 -0
- src/harness/src/openharness/services/session_backend.py +97 -0
- src/harness/src/openharness/services/session_memory/__init__.py +139 -0
- src/harness/src/openharness/services/session_storage.py +230 -0
- src/harness/src/openharness/services/token_estimation.py +15 -0
- src/harness/src/openharness/services/tool_outputs.py +55 -0
- src/harness/src/openharness/skills/__init__.py +44 -0
- src/harness/src/openharness/skills/_frontmatter.py +97 -0
- src/harness/src/openharness/skills/bundled/__init__.py +75 -0
- src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
- src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
- src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
- src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
- src/harness/src/openharness/skills/bundled/content/review.md +33 -0
- src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
- src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
- src/harness/src/openharness/skills/bundled/content/test.md +38 -0
- src/harness/src/openharness/skills/loader.py +229 -0
- src/harness/src/openharness/skills/registry.py +29 -0
- src/harness/src/openharness/skills/types.py +24 -0
- src/harness/src/openharness/state/__init__.py +6 -0
- src/harness/src/openharness/state/app_state.py +30 -0
- src/harness/src/openharness/state/store.py +40 -0
- src/harness/src/openharness/swarm/__init__.py +70 -0
- src/harness/src/openharness/swarm/in_process.py +693 -0
- src/harness/src/openharness/swarm/lockfile.py +24 -0
- src/harness/src/openharness/swarm/mailbox.py +522 -0
- src/harness/src/openharness/swarm/permission_sync.py +1168 -0
- src/harness/src/openharness/swarm/registry.py +410 -0
- src/harness/src/openharness/swarm/spawn_utils.py +229 -0
- src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
- src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
- src/harness/src/openharness/swarm/types.py +398 -0
- src/harness/src/openharness/swarm/worktree.py +315 -0
- src/harness/src/openharness/tasks/__init__.py +18 -0
- src/harness/src/openharness/tasks/local_agent_task.py +28 -0
- src/harness/src/openharness/tasks/local_shell_task.py +17 -0
- src/harness/src/openharness/tasks/manager.py +475 -0
- src/harness/src/openharness/tasks/stop_task.py +11 -0
- src/harness/src/openharness/tasks/types.py +32 -0
- src/harness/src/openharness/themes/__init__.py +21 -0
- src/harness/src/openharness/themes/builtin.py +89 -0
- src/harness/src/openharness/themes/loader.py +55 -0
- src/harness/src/openharness/themes/schema.py +54 -0
- src/harness/src/openharness/tools/__init__.py +107 -0
- src/harness/src/openharness/tools/agent_tool.py +141 -0
- src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
- src/harness/src/openharness/tools/base.py +80 -0
- src/harness/src/openharness/tools/bash_tool.py +259 -0
- src/harness/src/openharness/tools/brief_tool.py +33 -0
- src/harness/src/openharness/tools/config_tool.py +54 -0
- src/harness/src/openharness/tools/cron_create_tool.py +105 -0
- src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
- src/harness/src/openharness/tools/cron_list_tool.py +69 -0
- src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
- src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
- src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
- src/harness/src/openharness/tools/file_edit_tool.py +95 -0
- src/harness/src/openharness/tools/file_read_tool.py +72 -0
- src/harness/src/openharness/tools/file_write_tool.py +87 -0
- src/harness/src/openharness/tools/glob_tool.py +175 -0
- src/harness/src/openharness/tools/grep_tool.py +374 -0
- src/harness/src/openharness/tools/image_generation_tool.py +447 -0
- src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
- src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
- src/harness/src/openharness/tools/lsp_tool.py +154 -0
- src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
- src/harness/src/openharness/tools/mcp_tool.py +72 -0
- src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
- src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
- src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
- src/harness/src/openharness/tools/send_message_tool.py +58 -0
- src/harness/src/openharness/tools/skill_tool.py +43 -0
- src/harness/src/openharness/tools/sleep_tool.py +32 -0
- src/harness/src/openharness/tools/task_create_tool.py +56 -0
- src/harness/src/openharness/tools/task_get_tool.py +33 -0
- src/harness/src/openharness/tools/task_list_tool.py +35 -0
- src/harness/src/openharness/tools/task_output_tool.py +35 -0
- src/harness/src/openharness/tools/task_stop_tool.py +30 -0
- src/harness/src/openharness/tools/task_update_tool.py +50 -0
- src/harness/src/openharness/tools/team_create_tool.py +31 -0
- src/harness/src/openharness/tools/team_delete_tool.py +30 -0
- src/harness/src/openharness/tools/todo_write_tool.py +46 -0
- src/harness/src/openharness/tools/tool_search_tool.py +38 -0
- src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
- src/harness/src/openharness/tools/web_search_tool.py +119 -0
- src/harness/src/openharness/ui/__init__.py +5 -0
- src/harness/src/openharness/ui/app.py +320 -0
- src/harness/src/openharness/ui/backend_host.py +941 -0
- src/harness/src/openharness/ui/coordinator_drain.py +198 -0
- src/harness/src/openharness/ui/input.py +32 -0
- src/harness/src/openharness/ui/output.py +265 -0
- src/harness/src/openharness/ui/permission_dialog.py +14 -0
- src/harness/src/openharness/ui/protocol.py +247 -0
- src/harness/src/openharness/ui/react_launcher.py +179 -0
- src/harness/src/openharness/ui/runtime.py +799 -0
- src/harness/src/openharness/ui/textual_app.py +495 -0
- src/harness/src/openharness/utils/__init__.py +0 -0
- src/harness/src/openharness/utils/file_lock.py +86 -0
- src/harness/src/openharness/utils/fs.py +98 -0
- src/harness/src/openharness/utils/helpers.py +77 -0
- src/harness/src/openharness/utils/network_guard.py +340 -0
- src/harness/src/openharness/utils/shell.py +147 -0
- src/harness/src/openharness/vim/__init__.py +5 -0
- src/harness/src/openharness/vim/transitions.py +8 -0
- src/harness/src/openharness/voice/__init__.py +7 -0
- src/harness/src/openharness/voice/keyterms.py +10 -0
- src/harness/src/openharness/voice/stream_stt.py +8 -0
- src/harness/src/openharness/voice/voice_mode.py +44 -0
- src/llm/__init__.py +29 -0
- src/llm/image_generation.py +402 -0
- src/llm/provider_registry.py +419 -0
- src/llm/runtime_env.py +64 -0
- src/llm/structured_output.py +131 -0
- src/llm/vision.py +198 -0
- src/memory/__init__.py +2 -0
- src/memory/api/__init__.py +1 -0
- src/memory/api/base_memory_system_api.py +98 -0
- src/memory/api/base_symbolic_memory_system_api.py +190 -0
- src/memory/api/base_vector_memory_system_api.py +116 -0
- src/memory/api/faiss_memory_system_api.py +228 -0
- src/memory/api/slot_process_api.py +827 -0
- src/memory/api/symbolic_memory_system_api.py +572 -0
- src/memory/memory_system/__init__.py +48 -0
- src/memory/memory_system/component_taxonomy.py +430 -0
- src/memory/memory_system/llm.py +437 -0
- src/memory/memory_system/models.py +168 -0
- src/memory/memory_system/user_prompt.py +418 -0
- src/memory/memory_system/utils.py +282 -0
- src/memory/memory_system/vectorstore.py +311 -0
- src/memory/memory_system/working_slot.py +99 -0
- src/pipeline/__init__.py +129 -0
- src/pipeline/contracts.py +531 -0
- src/pipeline/discipline_taxonomy.py +916 -0
- src/pipeline/evidence_coverage_ledger.py +1150 -0
- src/pipeline/evidence_refinement.py +620 -0
- src/pipeline/experiment_to_symbolic.py +137 -0
- src/pipeline/multimodal_evidence/__init__.py +28 -0
- src/pipeline/multimodal_evidence/capabilities.py +107 -0
- src/pipeline/multimodal_evidence/claims.py +135 -0
- src/pipeline/multimodal_evidence/contract.py +291 -0
- src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
- src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
- src/pipeline/multimodal_evidence/manifest.py +254 -0
- src/pipeline/multimodal_evidence/native_analysis.py +479 -0
- src/pipeline/multimodal_evidence/perception.py +277 -0
- src/pipeline/multimodal_evidence/query_binding.py +65 -0
- src/pipeline/multimodal_evidence/rendering.py +120 -0
- src/pipeline/multimodal_evidence/safety.py +42 -0
- src/pipeline/multimodal_evidence/sampling.py +69 -0
- src/pipeline/multimodal_evidence/service.py +148 -0
- src/pipeline/multimodal_evidence/survey_integration.py +460 -0
- src/pipeline/paper_identity.py +52 -0
- src/pipeline/research_design_inventory.py +338 -0
- src/pipeline/research_domain_resolution.py +669 -0
- src/pipeline/research_identity.py +548 -0
- src/pipeline/research_question_contract.py +646 -0
- src/pipeline/retrieval_lanes.py +889 -0
- src/pipeline/run_loop.py +862 -0
- src/pipeline/science_events.py +30 -0
- src/pipeline/science_manifests.py +602 -0
- src/pipeline/science_run.py +918 -0
- src/pipeline/science_stages.py +709 -0
- src/pipeline/science_workflow.py +807 -0
- src/pipeline/sh_cluster_projection.py +306 -0
- src/pipeline/sh_graph_provenance.py +648 -0
- src/pipeline/subhypothesis_decomposition.py +392 -0
- src/pipeline/subhypothesis_evidence.py +856 -0
- src/pipeline/survey_evidence_plan.py +858 -0
- src/pipeline/survey_gap_adjudication.py +647 -0
- src/pipeline/survey_gap_candidates.py +840 -0
- src/pipeline/survey_gap_ledger.py +683 -0
- src/pipeline/survey_gap_triage.py +401 -0
- src/pipeline/survey_handoff_persistence.py +481 -0
- src/pipeline/survey_handoff_projection.py +457 -0
- src/pipeline/survey_idea_handoff.py +1112 -0
- src/pipeline/survey_idea_loader.py +289 -0
- 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)
|