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,148 @@
|
|
|
1
|
+
"""Adapter around the ``srt`` sandbox-runtime CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shlex
|
|
7
|
+
import shutil
|
|
8
|
+
import tempfile
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from openharness.config import Settings, load_settings
|
|
14
|
+
from openharness.platforms import get_platform, get_platform_capabilities
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SandboxUnavailableError(RuntimeError):
|
|
18
|
+
"""Raised when sandboxing is required but unavailable."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class SandboxAvailability:
|
|
23
|
+
"""Computed sandbox-runtime availability for the current environment."""
|
|
24
|
+
|
|
25
|
+
enabled: bool
|
|
26
|
+
available: bool
|
|
27
|
+
reason: str | None = None
|
|
28
|
+
command: str | None = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def active(self) -> bool:
|
|
32
|
+
"""Return whether sandboxing should be applied to child processes."""
|
|
33
|
+
return self.enabled and self.available
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_sandbox_runtime_config(settings: Settings) -> dict[str, Any]:
|
|
37
|
+
"""Convert OpenHarness settings into an ``srt`` settings payload."""
|
|
38
|
+
return {
|
|
39
|
+
"network": {
|
|
40
|
+
"allowedDomains": list(settings.sandbox.network.allowed_domains),
|
|
41
|
+
"deniedDomains": list(settings.sandbox.network.denied_domains),
|
|
42
|
+
},
|
|
43
|
+
"filesystem": {
|
|
44
|
+
"allowRead": list(settings.sandbox.filesystem.allow_read),
|
|
45
|
+
"denyRead": list(settings.sandbox.filesystem.deny_read),
|
|
46
|
+
"allowWrite": list(settings.sandbox.filesystem.allow_write),
|
|
47
|
+
"denyWrite": list(settings.sandbox.filesystem.deny_write),
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_sandbox_availability(settings: Settings | None = None) -> SandboxAvailability:
|
|
53
|
+
"""Return whether ``srt`` can be used for the current runtime."""
|
|
54
|
+
resolved_settings = settings or load_settings()
|
|
55
|
+
if not resolved_settings.sandbox.enabled:
|
|
56
|
+
return SandboxAvailability(enabled=False, available=False, reason="sandbox is disabled")
|
|
57
|
+
|
|
58
|
+
platform_name = get_platform()
|
|
59
|
+
capabilities = get_platform_capabilities(platform_name)
|
|
60
|
+
if not capabilities.supports_sandbox_runtime:
|
|
61
|
+
if platform_name == "windows":
|
|
62
|
+
reason = "sandbox runtime is not supported on native Windows; use WSL for sandboxed execution"
|
|
63
|
+
else:
|
|
64
|
+
reason = f"sandbox runtime is not supported on platform {platform_name}"
|
|
65
|
+
return SandboxAvailability(enabled=True, available=False, reason=reason)
|
|
66
|
+
|
|
67
|
+
enabled_platforms = {name.lower() for name in resolved_settings.sandbox.enabled_platforms}
|
|
68
|
+
if enabled_platforms and platform_name not in enabled_platforms:
|
|
69
|
+
return SandboxAvailability(
|
|
70
|
+
enabled=True,
|
|
71
|
+
available=False,
|
|
72
|
+
reason=f"sandbox is disabled for platform {platform_name} by configuration",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
srt = shutil.which("srt")
|
|
76
|
+
if not srt:
|
|
77
|
+
return SandboxAvailability(
|
|
78
|
+
enabled=True,
|
|
79
|
+
available=False,
|
|
80
|
+
reason=(
|
|
81
|
+
"sandbox runtime CLI not found; install it with "
|
|
82
|
+
"`npm install -g @anthropic-ai/sandbox-runtime`"
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if platform_name in {"linux", "wsl"} and shutil.which("bwrap") is None:
|
|
87
|
+
return SandboxAvailability(
|
|
88
|
+
enabled=True,
|
|
89
|
+
available=False,
|
|
90
|
+
reason="bubblewrap (`bwrap`) is required for sandbox runtime on Linux/WSL",
|
|
91
|
+
command=srt,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if platform_name == "macos" and shutil.which("sandbox-exec") is None:
|
|
95
|
+
return SandboxAvailability(
|
|
96
|
+
enabled=True,
|
|
97
|
+
available=False,
|
|
98
|
+
reason="`sandbox-exec` is required for sandbox runtime on macOS",
|
|
99
|
+
command=srt,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return SandboxAvailability(enabled=True, available=True, command=srt)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def wrap_command_for_sandbox(
|
|
106
|
+
command: list[str],
|
|
107
|
+
*,
|
|
108
|
+
settings: Settings | None = None,
|
|
109
|
+
) -> tuple[list[str], Path | None]:
|
|
110
|
+
"""Wrap an argv list with ``srt`` when sandboxing is active."""
|
|
111
|
+
resolved_settings = settings or load_settings()
|
|
112
|
+
if resolved_settings.sandbox.backend == "docker":
|
|
113
|
+
return command, None
|
|
114
|
+
availability = get_sandbox_availability(resolved_settings)
|
|
115
|
+
if not availability.active:
|
|
116
|
+
if resolved_settings.sandbox.enabled and resolved_settings.sandbox.fail_if_unavailable:
|
|
117
|
+
raise SandboxUnavailableError(availability.reason or "sandbox runtime is unavailable")
|
|
118
|
+
return command, None
|
|
119
|
+
|
|
120
|
+
settings_path = _write_runtime_settings(build_sandbox_runtime_config(resolved_settings))
|
|
121
|
+
# The ``srt`` argv form does not reliably preserve child exit codes for shell-style
|
|
122
|
+
# commands such as ``bash -lc 'exit 1'``. Build a single escaped command string and
|
|
123
|
+
# pass it through ``-c`` so hook/tool failures still propagate correctly.
|
|
124
|
+
wrapped = [
|
|
125
|
+
availability.command or "srt",
|
|
126
|
+
"--settings",
|
|
127
|
+
str(settings_path),
|
|
128
|
+
"-c",
|
|
129
|
+
shlex.join(command),
|
|
130
|
+
]
|
|
131
|
+
return wrapped, settings_path
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _write_runtime_settings(payload: dict[str, Any]) -> Path:
|
|
135
|
+
"""Persist a temporary settings file for one sandboxed child process."""
|
|
136
|
+
tmp = tempfile.NamedTemporaryFile(
|
|
137
|
+
mode="w",
|
|
138
|
+
encoding="utf-8",
|
|
139
|
+
prefix="openharness-sandbox-",
|
|
140
|
+
suffix=".json",
|
|
141
|
+
delete=False,
|
|
142
|
+
)
|
|
143
|
+
try:
|
|
144
|
+
json.dump(payload, tmp)
|
|
145
|
+
tmp.write("\n")
|
|
146
|
+
finally:
|
|
147
|
+
tmp.close()
|
|
148
|
+
return Path(tmp.name)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Docker-based sandbox backend for isolated tool execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from openharness.config import Settings
|
|
13
|
+
from openharness.platforms import get_platform, get_platform_capabilities
|
|
14
|
+
from openharness.sandbox.adapter import SandboxAvailability, SandboxUnavailableError
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_docker_availability(settings: Settings) -> SandboxAvailability:
|
|
20
|
+
"""Check whether Docker can be used as a sandbox backend."""
|
|
21
|
+
if not settings.sandbox.enabled or settings.sandbox.backend != "docker":
|
|
22
|
+
return SandboxAvailability(
|
|
23
|
+
enabled=False, available=False, reason="Docker sandbox is not enabled"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
platform_name = get_platform()
|
|
27
|
+
capabilities = get_platform_capabilities(platform_name)
|
|
28
|
+
if not capabilities.supports_docker_sandbox:
|
|
29
|
+
return SandboxAvailability(
|
|
30
|
+
enabled=True,
|
|
31
|
+
available=False,
|
|
32
|
+
reason=f"Docker sandbox is not supported on platform {platform_name}",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
docker = shutil.which("docker")
|
|
36
|
+
if not docker:
|
|
37
|
+
return SandboxAvailability(
|
|
38
|
+
enabled=True,
|
|
39
|
+
available=False,
|
|
40
|
+
reason="Docker CLI not found; install Docker Desktop or Docker Engine",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
subprocess.run(
|
|
45
|
+
[docker, "info"],
|
|
46
|
+
capture_output=True,
|
|
47
|
+
timeout=5,
|
|
48
|
+
check=True,
|
|
49
|
+
)
|
|
50
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError):
|
|
51
|
+
return SandboxAvailability(
|
|
52
|
+
enabled=True,
|
|
53
|
+
available=False,
|
|
54
|
+
reason="Docker daemon is not running",
|
|
55
|
+
command=docker,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return SandboxAvailability(enabled=True, available=True, command=docker)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class DockerSandboxSession:
|
|
63
|
+
"""Manages a long-running Docker container for one OpenHarness session."""
|
|
64
|
+
|
|
65
|
+
settings: Settings
|
|
66
|
+
session_id: str
|
|
67
|
+
cwd: Path
|
|
68
|
+
_container_name: str = field(init=False)
|
|
69
|
+
_running: bool = field(init=False, default=False)
|
|
70
|
+
|
|
71
|
+
def __post_init__(self) -> None:
|
|
72
|
+
self._container_name = f"openharness-sandbox-{self.session_id}"
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def container_name(self) -> str:
|
|
76
|
+
return self._container_name
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_running(self) -> bool:
|
|
80
|
+
return self._running
|
|
81
|
+
|
|
82
|
+
def _build_run_argv(self) -> list[str]:
|
|
83
|
+
"""Build the ``docker run`` argv for container creation."""
|
|
84
|
+
docker = shutil.which("docker") or "docker"
|
|
85
|
+
sandbox = self.settings.sandbox
|
|
86
|
+
docker_cfg = sandbox.docker
|
|
87
|
+
cwd_str = str(self.cwd.resolve())
|
|
88
|
+
|
|
89
|
+
argv = [
|
|
90
|
+
docker,
|
|
91
|
+
"run",
|
|
92
|
+
"-d",
|
|
93
|
+
"--rm",
|
|
94
|
+
"--name",
|
|
95
|
+
self._container_name,
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
# Docker backend currently supports only fully disabled networking.
|
|
99
|
+
# Domain-level allow/deny policies exist for the srt backend, but Docker
|
|
100
|
+
# does not enforce them yet. Fail closed instead of silently widening
|
|
101
|
+
# egress to unrestricted bridge networking.
|
|
102
|
+
if sandbox.network.allowed_domains or sandbox.network.denied_domains:
|
|
103
|
+
logger.warning(
|
|
104
|
+
"Docker sandbox does not enforce allowed_domains/denied_domains yet; "
|
|
105
|
+
"keeping network disabled"
|
|
106
|
+
)
|
|
107
|
+
argv.extend(["--network", "none"])
|
|
108
|
+
|
|
109
|
+
# Resource limits
|
|
110
|
+
if docker_cfg.cpu_limit > 0:
|
|
111
|
+
argv.extend(["--cpus", str(docker_cfg.cpu_limit)])
|
|
112
|
+
if docker_cfg.memory_limit:
|
|
113
|
+
argv.extend(["--memory", docker_cfg.memory_limit])
|
|
114
|
+
|
|
115
|
+
# Bind-mount project directory at the same path
|
|
116
|
+
argv.extend(["-v", f"{cwd_str}:{cwd_str}"])
|
|
117
|
+
argv.extend(["-w", cwd_str])
|
|
118
|
+
|
|
119
|
+
# Extra mounts
|
|
120
|
+
for mount in docker_cfg.extra_mounts:
|
|
121
|
+
argv.extend(["-v", mount])
|
|
122
|
+
|
|
123
|
+
# Extra environment variables
|
|
124
|
+
for key, value in docker_cfg.extra_env.items():
|
|
125
|
+
argv.extend(["-e", f"{key}={value}"])
|
|
126
|
+
|
|
127
|
+
argv.extend([docker_cfg.image, "tail", "-f", "/dev/null"])
|
|
128
|
+
return argv
|
|
129
|
+
|
|
130
|
+
async def start(self) -> None:
|
|
131
|
+
"""Create and start the sandbox container."""
|
|
132
|
+
from openharness.sandbox.docker_image import ensure_image_available
|
|
133
|
+
|
|
134
|
+
docker_cfg = self.settings.sandbox.docker
|
|
135
|
+
available = await ensure_image_available(
|
|
136
|
+
docker_cfg.image, docker_cfg.auto_build_image
|
|
137
|
+
)
|
|
138
|
+
if not available:
|
|
139
|
+
raise SandboxUnavailableError(
|
|
140
|
+
f"Docker image {docker_cfg.image!r} is not available and "
|
|
141
|
+
"auto_build_image is disabled"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
argv = self._build_run_argv()
|
|
145
|
+
logger.info("Starting Docker sandbox: %s", " ".join(argv))
|
|
146
|
+
|
|
147
|
+
process = await asyncio.create_subprocess_exec(
|
|
148
|
+
*argv,
|
|
149
|
+
stdout=asyncio.subprocess.PIPE,
|
|
150
|
+
stderr=asyncio.subprocess.PIPE,
|
|
151
|
+
)
|
|
152
|
+
stdout, stderr = await process.communicate()
|
|
153
|
+
if process.returncode != 0:
|
|
154
|
+
msg = stderr.decode("utf-8", errors="replace").strip()
|
|
155
|
+
raise SandboxUnavailableError(f"Failed to start Docker sandbox: {msg}")
|
|
156
|
+
|
|
157
|
+
self._running = True
|
|
158
|
+
logger.info("Docker sandbox started: %s", self._container_name)
|
|
159
|
+
|
|
160
|
+
async def stop(self) -> None:
|
|
161
|
+
"""Stop and remove the sandbox container."""
|
|
162
|
+
if not self._running:
|
|
163
|
+
return
|
|
164
|
+
docker = shutil.which("docker") or "docker"
|
|
165
|
+
try:
|
|
166
|
+
process = await asyncio.create_subprocess_exec(
|
|
167
|
+
docker,
|
|
168
|
+
"stop",
|
|
169
|
+
"-t",
|
|
170
|
+
"5",
|
|
171
|
+
self._container_name,
|
|
172
|
+
stdout=asyncio.subprocess.PIPE,
|
|
173
|
+
stderr=asyncio.subprocess.PIPE,
|
|
174
|
+
)
|
|
175
|
+
await asyncio.wait_for(process.communicate(), timeout=15)
|
|
176
|
+
except (asyncio.TimeoutError, OSError) as exc:
|
|
177
|
+
logger.warning("Error stopping Docker sandbox: %s", exc)
|
|
178
|
+
finally:
|
|
179
|
+
self._running = False
|
|
180
|
+
logger.info("Docker sandbox stopped: %s", self._container_name)
|
|
181
|
+
|
|
182
|
+
def stop_sync(self) -> None:
|
|
183
|
+
"""Synchronous stop for use in atexit handlers."""
|
|
184
|
+
if not self._running:
|
|
185
|
+
return
|
|
186
|
+
docker = shutil.which("docker") or "docker"
|
|
187
|
+
try:
|
|
188
|
+
subprocess.run(
|
|
189
|
+
[docker, "stop", "-t", "3", self._container_name],
|
|
190
|
+
capture_output=True,
|
|
191
|
+
timeout=10,
|
|
192
|
+
)
|
|
193
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
194
|
+
pass
|
|
195
|
+
finally:
|
|
196
|
+
self._running = False
|
|
197
|
+
|
|
198
|
+
async def exec_command(
|
|
199
|
+
self,
|
|
200
|
+
argv: list[str],
|
|
201
|
+
*,
|
|
202
|
+
cwd: str | Path,
|
|
203
|
+
stdin: int | None = None,
|
|
204
|
+
stdout: int | None = None,
|
|
205
|
+
stderr: int | None = None,
|
|
206
|
+
env: dict[str, str] | None = None,
|
|
207
|
+
) -> asyncio.subprocess.Process:
|
|
208
|
+
"""Execute a command inside the sandbox container.
|
|
209
|
+
|
|
210
|
+
Returns an ``asyncio.subprocess.Process`` with the same interface as
|
|
211
|
+
``asyncio.create_subprocess_exec``.
|
|
212
|
+
"""
|
|
213
|
+
if not self._running:
|
|
214
|
+
raise SandboxUnavailableError("Docker sandbox session is not running")
|
|
215
|
+
|
|
216
|
+
docker = shutil.which("docker") or "docker"
|
|
217
|
+
cmd: list[str] = [docker, "exec"]
|
|
218
|
+
cmd.extend(["-w", str(Path(cwd).resolve())])
|
|
219
|
+
|
|
220
|
+
if env:
|
|
221
|
+
for key, value in env.items():
|
|
222
|
+
cmd.extend(["-e", f"{key}={value}"])
|
|
223
|
+
|
|
224
|
+
cmd.append(self._container_name)
|
|
225
|
+
cmd.extend(argv)
|
|
226
|
+
|
|
227
|
+
return await asyncio.create_subprocess_exec(
|
|
228
|
+
*cmd,
|
|
229
|
+
stdin=stdin,
|
|
230
|
+
stdout=stdout,
|
|
231
|
+
stderr=stderr,
|
|
232
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Docker image availability and build helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_DEFAULT_IMAGE = "openharness-sandbox:latest"
|
|
13
|
+
|
|
14
|
+
_DOCKERFILE_CONTENT = """\
|
|
15
|
+
FROM python:3.11-slim
|
|
16
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
17
|
+
ripgrep bash git && \\
|
|
18
|
+
rm -rf /var/lib/apt/lists/*
|
|
19
|
+
RUN useradd -m -s /bin/bash ohuser
|
|
20
|
+
USER ohuser
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_dockerfile_content() -> str:
|
|
25
|
+
"""Return the default Dockerfile content for the sandbox image."""
|
|
26
|
+
return _DOCKERFILE_CONTENT
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def _image_exists(image: str) -> bool:
|
|
30
|
+
"""Check whether a Docker image exists locally."""
|
|
31
|
+
docker = shutil.which("docker") or "docker"
|
|
32
|
+
process = await asyncio.create_subprocess_exec(
|
|
33
|
+
docker,
|
|
34
|
+
"image",
|
|
35
|
+
"inspect",
|
|
36
|
+
image,
|
|
37
|
+
stdout=asyncio.subprocess.PIPE,
|
|
38
|
+
stderr=asyncio.subprocess.PIPE,
|
|
39
|
+
)
|
|
40
|
+
await process.communicate()
|
|
41
|
+
return process.returncode == 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def build_default_image(image: str = _DEFAULT_IMAGE) -> bool:
|
|
45
|
+
"""Build the default sandbox image from the bundled Dockerfile.
|
|
46
|
+
|
|
47
|
+
Returns ``True`` on success, ``False`` on failure.
|
|
48
|
+
"""
|
|
49
|
+
docker = shutil.which("docker") or "docker"
|
|
50
|
+
dockerfile_path = Path(__file__).parent / "Dockerfile"
|
|
51
|
+
|
|
52
|
+
if dockerfile_path.exists():
|
|
53
|
+
cmd = [docker, "build", "-t", image, "-f", str(dockerfile_path), str(dockerfile_path.parent)]
|
|
54
|
+
else:
|
|
55
|
+
# Fallback: pipe Dockerfile content via stdin
|
|
56
|
+
cmd = [docker, "build", "-t", image, "-"]
|
|
57
|
+
|
|
58
|
+
logger.info("Building Docker sandbox image %r ...", image)
|
|
59
|
+
|
|
60
|
+
if dockerfile_path.exists():
|
|
61
|
+
process = await asyncio.create_subprocess_exec(
|
|
62
|
+
*cmd,
|
|
63
|
+
stdout=asyncio.subprocess.PIPE,
|
|
64
|
+
stderr=asyncio.subprocess.PIPE,
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
process = await asyncio.create_subprocess_exec(
|
|
68
|
+
*cmd,
|
|
69
|
+
stdin=asyncio.subprocess.PIPE,
|
|
70
|
+
stdout=asyncio.subprocess.PIPE,
|
|
71
|
+
stderr=asyncio.subprocess.PIPE,
|
|
72
|
+
)
|
|
73
|
+
await process.communicate(input=_DOCKERFILE_CONTENT.encode("utf-8"))
|
|
74
|
+
if process.returncode == 0:
|
|
75
|
+
logger.info("Docker sandbox image %r built successfully", image)
|
|
76
|
+
return True
|
|
77
|
+
logger.warning("Failed to build Docker sandbox image %r", image)
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
_, stderr_bytes = await process.communicate()
|
|
81
|
+
if process.returncode == 0:
|
|
82
|
+
logger.info("Docker sandbox image %r built successfully", image)
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
logger.warning(
|
|
86
|
+
"Failed to build Docker sandbox image %r: %s",
|
|
87
|
+
image,
|
|
88
|
+
stderr_bytes.decode("utf-8", errors="replace").strip(),
|
|
89
|
+
)
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def ensure_image_available(image: str, auto_build: bool) -> bool:
|
|
94
|
+
"""Ensure the sandbox image exists, optionally building it.
|
|
95
|
+
|
|
96
|
+
Returns ``True`` if the image is available.
|
|
97
|
+
"""
|
|
98
|
+
if await _image_exists(image):
|
|
99
|
+
return True
|
|
100
|
+
if not auto_build:
|
|
101
|
+
logger.warning("Docker image %r not found and auto_build_image is disabled", image)
|
|
102
|
+
return False
|
|
103
|
+
return await build_default_image(image)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Path boundary enforcement for sandbox file operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate_sandbox_path(
|
|
9
|
+
path: Path,
|
|
10
|
+
cwd: Path,
|
|
11
|
+
extra_allowed: list[str] | None = None,
|
|
12
|
+
) -> tuple[bool, str]:
|
|
13
|
+
"""Check whether *path* falls within the sandbox boundary.
|
|
14
|
+
|
|
15
|
+
Returns ``(True, "")`` when the path is allowed, or ``(False, reason)``
|
|
16
|
+
when it falls outside the permitted directories.
|
|
17
|
+
"""
|
|
18
|
+
resolved = path.resolve()
|
|
19
|
+
resolved_cwd = cwd.resolve()
|
|
20
|
+
|
|
21
|
+
# Primary check: path must be within the project directory
|
|
22
|
+
try:
|
|
23
|
+
resolved.relative_to(resolved_cwd)
|
|
24
|
+
return True, ""
|
|
25
|
+
except ValueError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
# Secondary: check extra allowed paths (from filesystem settings)
|
|
29
|
+
for allowed in extra_allowed or []:
|
|
30
|
+
allowed_path = Path(allowed).expanduser().resolve()
|
|
31
|
+
try:
|
|
32
|
+
resolved.relative_to(allowed_path)
|
|
33
|
+
return True, ""
|
|
34
|
+
except ValueError:
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
return False, f"path {resolved} is outside the sandbox boundary ({resolved_cwd})"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Module-level Docker sandbox session registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from openharness.config import Settings
|
|
12
|
+
from openharness.sandbox.docker_backend import DockerSandboxSession
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
_active_session: DockerSandboxSession | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_docker_sandbox():
|
|
20
|
+
"""Return the active Docker sandbox session, or ``None``."""
|
|
21
|
+
return _active_session
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_docker_sandbox_active() -> bool:
|
|
25
|
+
"""Return whether a Docker sandbox session is currently running."""
|
|
26
|
+
return _active_session is not None and _active_session.is_running
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def start_docker_sandbox(
|
|
30
|
+
settings: Settings,
|
|
31
|
+
session_id: str,
|
|
32
|
+
cwd: Path,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Start a Docker sandbox session for the current OpenHarness session."""
|
|
35
|
+
global _active_session # noqa: PLW0603
|
|
36
|
+
|
|
37
|
+
from openharness.sandbox.docker_backend import DockerSandboxSession, get_docker_availability
|
|
38
|
+
|
|
39
|
+
availability = get_docker_availability(settings)
|
|
40
|
+
if not availability.available:
|
|
41
|
+
if settings.sandbox.fail_if_unavailable:
|
|
42
|
+
from openharness.sandbox.adapter import SandboxUnavailableError
|
|
43
|
+
|
|
44
|
+
raise SandboxUnavailableError(
|
|
45
|
+
availability.reason or "Docker sandbox is unavailable"
|
|
46
|
+
)
|
|
47
|
+
logger.warning("Docker sandbox unavailable: %s", availability.reason)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
session = DockerSandboxSession(settings=settings, session_id=session_id, cwd=cwd)
|
|
51
|
+
await session.start()
|
|
52
|
+
_active_session = session
|
|
53
|
+
|
|
54
|
+
# Safety net: stop the container if the process exits without close_runtime()
|
|
55
|
+
atexit.register(session.stop_sync)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def stop_docker_sandbox() -> None:
|
|
59
|
+
"""Stop the active Docker sandbox session, if any."""
|
|
60
|
+
global _active_session # noqa: PLW0603
|
|
61
|
+
if _active_session is not None:
|
|
62
|
+
await _active_session.stop()
|
|
63
|
+
_active_session = None
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Service exports."""
|
|
2
|
+
|
|
3
|
+
from openharness.services.compact import (
|
|
4
|
+
build_post_compact_messages,
|
|
5
|
+
compact_conversation,
|
|
6
|
+
compact_messages,
|
|
7
|
+
estimate_conversation_tokens,
|
|
8
|
+
summarize_messages,
|
|
9
|
+
)
|
|
10
|
+
from openharness.services.session_storage import (
|
|
11
|
+
export_session_markdown,
|
|
12
|
+
get_project_session_dir,
|
|
13
|
+
load_session_snapshot,
|
|
14
|
+
save_session_snapshot,
|
|
15
|
+
)
|
|
16
|
+
from openharness.services.token_estimation import estimate_message_tokens, estimate_tokens
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"compact_messages",
|
|
20
|
+
"compact_conversation",
|
|
21
|
+
"build_post_compact_messages",
|
|
22
|
+
"estimate_conversation_tokens",
|
|
23
|
+
"estimate_message_tokens",
|
|
24
|
+
"estimate_tokens",
|
|
25
|
+
"export_session_markdown",
|
|
26
|
+
"get_project_session_dir",
|
|
27
|
+
"load_session_snapshot",
|
|
28
|
+
"save_session_snapshot",
|
|
29
|
+
"summarize_messages",
|
|
30
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Automatic memory consolidation (auto-dream)."""
|
|
2
|
+
|
|
3
|
+
from openharness.services.autodream.backup import (
|
|
4
|
+
create_memory_backup,
|
|
5
|
+
diff_memory_dirs,
|
|
6
|
+
format_memory_diff,
|
|
7
|
+
latest_memory_backup,
|
|
8
|
+
restore_memory_backup,
|
|
9
|
+
)
|
|
10
|
+
from openharness.services.autodream.lock import (
|
|
11
|
+
list_sessions_touched_since,
|
|
12
|
+
read_last_consolidated_at,
|
|
13
|
+
record_consolidation,
|
|
14
|
+
rollback_consolidation_lock,
|
|
15
|
+
try_acquire_consolidation_lock,
|
|
16
|
+
)
|
|
17
|
+
from openharness.services.autodream.prompt import build_consolidation_prompt
|
|
18
|
+
from openharness.services.autodream.service import execute_auto_dream, start_dream_now
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"build_consolidation_prompt",
|
|
22
|
+
"create_memory_backup",
|
|
23
|
+
"diff_memory_dirs",
|
|
24
|
+
"execute_auto_dream",
|
|
25
|
+
"format_memory_diff",
|
|
26
|
+
"latest_memory_backup",
|
|
27
|
+
"list_sessions_touched_since",
|
|
28
|
+
"read_last_consolidated_at",
|
|
29
|
+
"record_consolidation",
|
|
30
|
+
"restore_memory_backup",
|
|
31
|
+
"rollback_consolidation_lock",
|
|
32
|
+
"start_dream_now",
|
|
33
|
+
"try_acquire_consolidation_lock",
|
|
34
|
+
]
|