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,153 @@
|
|
|
1
|
+
"""Usage index for recalled memory entries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from openharness.memory.paths import get_project_memory_dir
|
|
11
|
+
from openharness.memory.scan import scan_memory_files
|
|
12
|
+
from openharness.memory.schema import format_datetime, parse_datetime, utc_now
|
|
13
|
+
from openharness.memory.types import MemoryHeader
|
|
14
|
+
from openharness.utils.file_lock import exclusive_file_lock
|
|
15
|
+
from openharness.utils.fs import atomic_write_text
|
|
16
|
+
|
|
17
|
+
USAGE_INDEX_NAME = "usage_index.json"
|
|
18
|
+
STALE_UNUSED_DAYS = 60
|
|
19
|
+
STALE_MAX_IMPORTANCE = 1
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_usage_index_path(cwd: str | Path, *, memory_dir: str | Path | None = None) -> Path:
|
|
23
|
+
"""Return the usage index path for a memory store."""
|
|
24
|
+
|
|
25
|
+
root = Path(memory_dir) if memory_dir is not None else get_project_memory_dir(cwd)
|
|
26
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
return root / USAGE_INDEX_NAME
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_usage_index(cwd: str | Path, *, memory_dir: str | Path | None = None) -> dict[str, Any]:
|
|
31
|
+
"""Load usage index data, returning an empty index for invalid files."""
|
|
32
|
+
|
|
33
|
+
path = get_usage_index_path(cwd, memory_dir=memory_dir)
|
|
34
|
+
if not path.exists():
|
|
35
|
+
return _empty_index()
|
|
36
|
+
try:
|
|
37
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
38
|
+
except (OSError, json.JSONDecodeError):
|
|
39
|
+
return _empty_index()
|
|
40
|
+
if not isinstance(data, dict):
|
|
41
|
+
return _empty_index()
|
|
42
|
+
memories = data.get("memories")
|
|
43
|
+
if not isinstance(memories, dict):
|
|
44
|
+
memories = {}
|
|
45
|
+
normalized = _empty_index()
|
|
46
|
+
normalized["memories"] = {
|
|
47
|
+
str(memory_id): _normalize_usage_record(record)
|
|
48
|
+
for memory_id, record in memories.items()
|
|
49
|
+
if isinstance(record, dict)
|
|
50
|
+
}
|
|
51
|
+
return normalized
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def save_usage_index(
|
|
55
|
+
cwd: str | Path,
|
|
56
|
+
index: dict[str, Any],
|
|
57
|
+
*,
|
|
58
|
+
memory_dir: str | Path | None = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Persist usage index data atomically."""
|
|
61
|
+
|
|
62
|
+
path = get_usage_index_path(cwd, memory_dir=memory_dir)
|
|
63
|
+
payload = json.dumps(index, indent=2, ensure_ascii=False, sort_keys=True) + "\n"
|
|
64
|
+
atomic_write_text(path, payload)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_memory_usage(
|
|
68
|
+
cwd: str | Path,
|
|
69
|
+
memory_id: str,
|
|
70
|
+
*,
|
|
71
|
+
memory_dir: str | Path | None = None,
|
|
72
|
+
) -> dict[str, Any]:
|
|
73
|
+
"""Return usage data for a memory id."""
|
|
74
|
+
|
|
75
|
+
if not memory_id:
|
|
76
|
+
return _normalize_usage_record({})
|
|
77
|
+
index = load_usage_index(cwd, memory_dir=memory_dir)
|
|
78
|
+
record = index["memories"].get(memory_id, {})
|
|
79
|
+
return _normalize_usage_record(record)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def mark_memory_used(
|
|
83
|
+
cwd: str | Path,
|
|
84
|
+
memories: list[MemoryHeader],
|
|
85
|
+
*,
|
|
86
|
+
memory_dir: str | Path | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Record that memory entries were recalled into a runtime prompt."""
|
|
89
|
+
|
|
90
|
+
usable = [header for header in memories if header.id]
|
|
91
|
+
if not usable:
|
|
92
|
+
return
|
|
93
|
+
resolved_memory_dir = Path(memory_dir) if memory_dir is not None else usable[0].path.parent
|
|
94
|
+
lock_path = resolved_memory_dir / ".usage_index.lock"
|
|
95
|
+
with exclusive_file_lock(lock_path):
|
|
96
|
+
index = load_usage_index(cwd, memory_dir=resolved_memory_dir)
|
|
97
|
+
now = format_datetime(utc_now())
|
|
98
|
+
for header in usable:
|
|
99
|
+
record = _normalize_usage_record(index["memories"].get(header.id, {}))
|
|
100
|
+
record["use_count"] = int(record["use_count"]) + 1
|
|
101
|
+
record["last_used_at"] = now
|
|
102
|
+
record["path"] = header.path.name
|
|
103
|
+
index["memories"][header.id] = record
|
|
104
|
+
save_usage_index(cwd, index, memory_dir=resolved_memory_dir)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def find_stale_memory_candidates(
|
|
108
|
+
cwd: str | Path,
|
|
109
|
+
*,
|
|
110
|
+
memory_dir: str | Path | None = None,
|
|
111
|
+
) -> list[MemoryHeader]:
|
|
112
|
+
"""Return low-value unused memories that auto-dream should review for pruning."""
|
|
113
|
+
|
|
114
|
+
resolved_memory_dir = Path(memory_dir) if memory_dir is not None else None
|
|
115
|
+
headers = scan_memory_files(
|
|
116
|
+
cwd,
|
|
117
|
+
max_files=None,
|
|
118
|
+
include_disabled=False,
|
|
119
|
+
include_expired=False,
|
|
120
|
+
memory_dir=resolved_memory_dir,
|
|
121
|
+
)
|
|
122
|
+
now = utc_now()
|
|
123
|
+
candidates: list[MemoryHeader] = []
|
|
124
|
+
for header in headers:
|
|
125
|
+
if header.importance > STALE_MAX_IMPORTANCE:
|
|
126
|
+
continue
|
|
127
|
+
usage = get_memory_usage(cwd, header.id, memory_dir=resolved_memory_dir or header.path.parent)
|
|
128
|
+
if int(usage["use_count"]) > 0:
|
|
129
|
+
continue
|
|
130
|
+
updated_at = parse_datetime(header.updated_at) or parse_datetime(header.created_at)
|
|
131
|
+
if updated_at is None:
|
|
132
|
+
continue
|
|
133
|
+
if now - updated_at >= timedelta(days=STALE_UNUSED_DAYS):
|
|
134
|
+
candidates.append(header)
|
|
135
|
+
candidates.sort(key=lambda item: (item.importance, item.updated_at or "", item.path.name))
|
|
136
|
+
return candidates
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _empty_index() -> dict[str, Any]:
|
|
140
|
+
return {"version": 1, "memories": {}}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _normalize_usage_record(record: dict[str, Any]) -> dict[str, Any]:
|
|
144
|
+
use_count = record.get("use_count", 0)
|
|
145
|
+
try:
|
|
146
|
+
use_count = max(0, int(use_count))
|
|
147
|
+
except (TypeError, ValueError):
|
|
148
|
+
use_count = 0
|
|
149
|
+
return {
|
|
150
|
+
"last_used_at": str(record.get("last_used_at") or ""),
|
|
151
|
+
"use_count": use_count,
|
|
152
|
+
"path": str(record.get("path") or ""),
|
|
153
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Output style loading."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from openharness.config.paths import get_config_dir
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class OutputStyle:
|
|
13
|
+
"""A named output style."""
|
|
14
|
+
|
|
15
|
+
name: str
|
|
16
|
+
content: str
|
|
17
|
+
source: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_output_styles_dir() -> Path:
|
|
21
|
+
"""Return the custom output styles directory."""
|
|
22
|
+
path = get_config_dir() / "output_styles"
|
|
23
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
return path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_output_styles() -> list[OutputStyle]:
|
|
28
|
+
"""Load bundled and custom output styles."""
|
|
29
|
+
styles = [
|
|
30
|
+
OutputStyle(name="default", content="Standard rich console output.", source="builtin"),
|
|
31
|
+
OutputStyle(name="minimal", content="Very terse plain-text output.", source="builtin"),
|
|
32
|
+
OutputStyle(name="codex", content="Codex-like compact transcript and tool output.", source="builtin"),
|
|
33
|
+
]
|
|
34
|
+
for path in sorted(get_output_styles_dir().glob("*.md")):
|
|
35
|
+
styles.append(
|
|
36
|
+
OutputStyle(
|
|
37
|
+
name=path.stem,
|
|
38
|
+
content=path.read_text(encoding="utf-8"),
|
|
39
|
+
source="user",
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
return styles
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Permission helpers for OpenHarness."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
8
|
+
from openharness.permissions.checker import PermissionChecker, PermissionDecision
|
|
9
|
+
from openharness.permissions.modes import PermissionMode
|
|
10
|
+
|
|
11
|
+
__all__ = ["PermissionChecker", "PermissionDecision", "PermissionMode"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __getattr__(name: str):
|
|
15
|
+
if name in {"PermissionChecker", "PermissionDecision"}:
|
|
16
|
+
from openharness.permissions.checker import PermissionChecker, PermissionDecision
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
"PermissionChecker": PermissionChecker,
|
|
20
|
+
"PermissionDecision": PermissionDecision,
|
|
21
|
+
}[name]
|
|
22
|
+
if name == "PermissionMode":
|
|
23
|
+
from openharness.permissions.modes import PermissionMode
|
|
24
|
+
|
|
25
|
+
return PermissionMode
|
|
26
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Permission checking for tool execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import fnmatch
|
|
6
|
+
import logging
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from openharness.config.settings import PermissionSettings
|
|
10
|
+
from openharness.permissions.modes import PermissionMode
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Paths that are always denied regardless of permission mode or user config.
|
|
15
|
+
# These protect high-value credential and key material from LLM-directed access
|
|
16
|
+
# (including via prompt injection). Patterns use fnmatch syntax and are matched
|
|
17
|
+
# against the fully-resolved absolute path produced by the query engine.
|
|
18
|
+
SENSITIVE_PATH_PATTERNS: tuple[str, ...] = (
|
|
19
|
+
# SSH keys and config
|
|
20
|
+
"*/.ssh/*",
|
|
21
|
+
# AWS credentials
|
|
22
|
+
"*/.aws/credentials",
|
|
23
|
+
"*/.aws/config",
|
|
24
|
+
# GCP credentials
|
|
25
|
+
"*/.config/gcloud/*",
|
|
26
|
+
# Azure credentials
|
|
27
|
+
"*/.azure/*",
|
|
28
|
+
# GPG keys
|
|
29
|
+
"*/.gnupg/*",
|
|
30
|
+
# Docker credentials
|
|
31
|
+
"*/.docker/config.json",
|
|
32
|
+
# Kubernetes credentials
|
|
33
|
+
"*/.kube/config",
|
|
34
|
+
# OpenHarness own credential stores
|
|
35
|
+
"*/.openharness/credentials.json",
|
|
36
|
+
"*/.openharness/copilot_auth.json",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class PermissionDecision:
|
|
42
|
+
"""Result of checking whether a tool invocation may run."""
|
|
43
|
+
|
|
44
|
+
allowed: bool
|
|
45
|
+
requires_confirmation: bool = False
|
|
46
|
+
reason: str = ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class PathRule:
|
|
51
|
+
"""A glob-based path permission rule."""
|
|
52
|
+
|
|
53
|
+
pattern: str
|
|
54
|
+
allow: bool # True = allow, False = deny
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PermissionChecker:
|
|
58
|
+
"""Evaluate tool usage against the configured permission mode and rules."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, settings: PermissionSettings) -> None:
|
|
61
|
+
self._settings = settings
|
|
62
|
+
# Parse path rules from settings
|
|
63
|
+
self._path_rules: list[PathRule] = []
|
|
64
|
+
for rule in getattr(settings, "path_rules", []):
|
|
65
|
+
pattern = getattr(rule, "pattern", None) or (rule.get("pattern") if isinstance(rule, dict) else None)
|
|
66
|
+
allow = getattr(rule, "allow", True) if not isinstance(rule, dict) else rule.get("allow", True)
|
|
67
|
+
if isinstance(pattern, str) and pattern.strip():
|
|
68
|
+
self._path_rules.append(PathRule(pattern=pattern.strip(), allow=allow))
|
|
69
|
+
else:
|
|
70
|
+
log.warning(
|
|
71
|
+
"Skipping path rule with missing, empty, or non-string 'pattern' field: %r",
|
|
72
|
+
rule,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def evaluate(
|
|
76
|
+
self,
|
|
77
|
+
tool_name: str,
|
|
78
|
+
*,
|
|
79
|
+
is_read_only: bool,
|
|
80
|
+
file_path: str | None = None,
|
|
81
|
+
command: str | None = None,
|
|
82
|
+
) -> PermissionDecision:
|
|
83
|
+
"""Return whether the tool may run immediately."""
|
|
84
|
+
# Built-in sensitive path protection — always active, cannot be
|
|
85
|
+
# overridden by user settings or permission mode. This is a
|
|
86
|
+
# defence-in-depth measure against LLM-directed or prompt-injection
|
|
87
|
+
# driven access to credential files.
|
|
88
|
+
if file_path:
|
|
89
|
+
for candidate_path in _policy_match_paths(file_path):
|
|
90
|
+
for pattern in SENSITIVE_PATH_PATTERNS:
|
|
91
|
+
if fnmatch.fnmatch(candidate_path, pattern):
|
|
92
|
+
return PermissionDecision(
|
|
93
|
+
allowed=False,
|
|
94
|
+
reason=(
|
|
95
|
+
f"Access denied: {file_path} is a sensitive credential path "
|
|
96
|
+
f"(matched built-in pattern '{pattern}')"
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Explicit tool deny list
|
|
101
|
+
if tool_name in self._settings.denied_tools:
|
|
102
|
+
return PermissionDecision(allowed=False, reason=f"{tool_name} is explicitly denied")
|
|
103
|
+
|
|
104
|
+
# Explicit tool allow list
|
|
105
|
+
if tool_name in self._settings.allowed_tools:
|
|
106
|
+
return PermissionDecision(allowed=True, reason=f"{tool_name} is explicitly allowed")
|
|
107
|
+
|
|
108
|
+
# Check path-level rules
|
|
109
|
+
if file_path and self._path_rules:
|
|
110
|
+
for candidate_path in _policy_match_paths(file_path):
|
|
111
|
+
for rule in self._path_rules:
|
|
112
|
+
if fnmatch.fnmatch(candidate_path, rule.pattern):
|
|
113
|
+
if not rule.allow:
|
|
114
|
+
return PermissionDecision(
|
|
115
|
+
allowed=False,
|
|
116
|
+
reason=f"Path {file_path} matches deny rule: {rule.pattern}",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Check command deny patterns (e.g. deny "rm -rf /")
|
|
120
|
+
if command:
|
|
121
|
+
for pattern in getattr(self._settings, "denied_commands", []):
|
|
122
|
+
if isinstance(pattern, str) and fnmatch.fnmatch(command, pattern):
|
|
123
|
+
return PermissionDecision(
|
|
124
|
+
allowed=False,
|
|
125
|
+
reason=f"Command matches deny pattern: {pattern}",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Full auto: allow everything
|
|
129
|
+
if self._settings.mode == PermissionMode.FULL_AUTO:
|
|
130
|
+
return PermissionDecision(allowed=True, reason="Auto mode allows all tools")
|
|
131
|
+
|
|
132
|
+
# Read-only tools always allowed
|
|
133
|
+
if is_read_only:
|
|
134
|
+
return PermissionDecision(allowed=True, reason="read-only tools are allowed")
|
|
135
|
+
|
|
136
|
+
# Plan mode: block mutating tools
|
|
137
|
+
if self._settings.mode == PermissionMode.PLAN:
|
|
138
|
+
return PermissionDecision(
|
|
139
|
+
allowed=False,
|
|
140
|
+
reason="Plan mode blocks mutating tools until the user exits plan mode",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Default mode: require confirmation for mutating tools
|
|
144
|
+
bash_hint = _bash_permission_hint(command)
|
|
145
|
+
reason = (
|
|
146
|
+
"Mutating tools require user confirmation in default mode. "
|
|
147
|
+
"Approve the prompt when asked, or run /permissions full_auto "
|
|
148
|
+
"if you want to allow them for this session."
|
|
149
|
+
)
|
|
150
|
+
if bash_hint:
|
|
151
|
+
reason = f"{reason} {bash_hint}"
|
|
152
|
+
return PermissionDecision(
|
|
153
|
+
allowed=False,
|
|
154
|
+
requires_confirmation=True,
|
|
155
|
+
reason=reason,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _policy_match_paths(file_path: str) -> tuple[str, ...]:
|
|
160
|
+
"""Return path forms that should participate in policy matching.
|
|
161
|
+
|
|
162
|
+
Directory-scoped tools like ``grep`` and ``glob`` may operate on a root such
|
|
163
|
+
as ``/home/user/.ssh``. Appending a trailing slash lets glob-style deny
|
|
164
|
+
patterns like ``*/.ssh/*`` and ``/etc/*`` match the directory root itself.
|
|
165
|
+
"""
|
|
166
|
+
normalized = file_path.rstrip("/")
|
|
167
|
+
if not normalized:
|
|
168
|
+
return (file_path,)
|
|
169
|
+
return (normalized, normalized + "/")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _bash_permission_hint(command: str | None) -> str:
|
|
173
|
+
if not command:
|
|
174
|
+
return ""
|
|
175
|
+
lowered = command.lower()
|
|
176
|
+
install_markers = (
|
|
177
|
+
"npm install",
|
|
178
|
+
"pnpm install",
|
|
179
|
+
"yarn install",
|
|
180
|
+
"bun install",
|
|
181
|
+
"pip install",
|
|
182
|
+
"uv pip install",
|
|
183
|
+
"poetry install",
|
|
184
|
+
"cargo install",
|
|
185
|
+
"create-next-app",
|
|
186
|
+
"npm create ",
|
|
187
|
+
"pnpm create ",
|
|
188
|
+
"yarn create ",
|
|
189
|
+
"bun create ",
|
|
190
|
+
"npx create-",
|
|
191
|
+
"npm init ",
|
|
192
|
+
"pnpm init ",
|
|
193
|
+
"yarn init ",
|
|
194
|
+
)
|
|
195
|
+
if any(marker in lowered for marker in install_markers):
|
|
196
|
+
return (
|
|
197
|
+
"Package installation and scaffolding commands change the workspace, "
|
|
198
|
+
"so they will not run automatically in default mode."
|
|
199
|
+
)
|
|
200
|
+
return ""
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Session personalization — auto-extract local rules from conversation history."""
|
|
2
|
+
|
|
3
|
+
from openharness.personalization.extractor import extract_local_rules
|
|
4
|
+
from openharness.personalization.rules import load_local_rules, save_local_rules
|
|
5
|
+
|
|
6
|
+
__all__ = ["extract_local_rules", "load_local_rules", "save_local_rules"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Extract local rules from session conversation history."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
# Patterns that indicate environment-specific facts worth capturing
|
|
11
|
+
_FACT_PATTERNS: list[tuple[str, str, re.Pattern]] = [
|
|
12
|
+
("ssh_host", "SSH connection", re.compile(
|
|
13
|
+
r"ssh\s+(?:-[io]\s+\S+\s+)*(\S+@[\d.]+|\S+@\S+)", re.IGNORECASE
|
|
14
|
+
)),
|
|
15
|
+
("ip_address", "Server IP", re.compile(
|
|
16
|
+
r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
|
|
17
|
+
)),
|
|
18
|
+
("data_path", "Data path", re.compile(
|
|
19
|
+
r"(/(?:ext|mnt|home|data|root)\S*/(?:data\S*|landing|derived|reference)\S*)"
|
|
20
|
+
)),
|
|
21
|
+
("conda_env", "Conda environment", re.compile(
|
|
22
|
+
r"conda\s+activate\s+(\S+)"
|
|
23
|
+
)),
|
|
24
|
+
("python_env", "Python version", re.compile(
|
|
25
|
+
r"[Pp]ython\s*(3\.\d+(?:\.\d+)?)"
|
|
26
|
+
)),
|
|
27
|
+
("api_endpoint", "API endpoint", re.compile(
|
|
28
|
+
r"(https?://\S+/v\d+/?)\b"
|
|
29
|
+
)),
|
|
30
|
+
("env_var", "Environment variable", re.compile(
|
|
31
|
+
r"export\s+([A-Z][A-Z0-9_]+)(?:=\S+)?"
|
|
32
|
+
)),
|
|
33
|
+
("git_remote", "Git remote", re.compile(
|
|
34
|
+
r"(?:github|gitlab)\.com[:/](\S+?)(?:\.git)?"
|
|
35
|
+
)),
|
|
36
|
+
("ray_cluster", "Ray cluster", re.compile(
|
|
37
|
+
r"ray\s+(?:start|init|submit)\b.*?(--address\s+\S+|\d+\.\d+\.\d+\.\d+:\d+)",
|
|
38
|
+
re.IGNORECASE,
|
|
39
|
+
)),
|
|
40
|
+
("cron_schedule", "Cron schedule", re.compile(
|
|
41
|
+
r"((?:\d+|\*)\s+(?:\d+|\*)\s+(?:\d+|\*)\s+(?:\d+|\*)\s+(?:\d+|\*))\s+\S+"
|
|
42
|
+
)),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def extract_facts_from_text(text: str) -> list[dict]:
|
|
47
|
+
"""Extract environment-specific facts from conversation text using patterns."""
|
|
48
|
+
facts = []
|
|
49
|
+
seen_keys = set()
|
|
50
|
+
|
|
51
|
+
for fact_type, label, pattern in _FACT_PATTERNS:
|
|
52
|
+
for match in pattern.finditer(text):
|
|
53
|
+
value = match.group(1) if match.lastindex else match.group(0)
|
|
54
|
+
value = value.strip().rstrip(".,;:)")
|
|
55
|
+
if not value or len(value) < 3:
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# Skip common false positives
|
|
59
|
+
if fact_type == "ip_address" and value.startswith(("0.", "255.", "127.0.0.1")):
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
key = f"{fact_type}:{value}"
|
|
63
|
+
if key in seen_keys:
|
|
64
|
+
continue
|
|
65
|
+
seen_keys.add(key)
|
|
66
|
+
|
|
67
|
+
facts.append({
|
|
68
|
+
"key": key,
|
|
69
|
+
"type": fact_type,
|
|
70
|
+
"label": label,
|
|
71
|
+
"value": value,
|
|
72
|
+
"confidence": 0.7,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return facts
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def extract_local_rules(session_messages: list[dict]) -> list[dict]:
|
|
79
|
+
"""Extract environment facts from a list of session messages.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
session_messages: List of message dicts with 'role' and 'content' keys.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of fact dicts with key, type, label, value, confidence.
|
|
86
|
+
"""
|
|
87
|
+
all_text = []
|
|
88
|
+
for msg in session_messages:
|
|
89
|
+
content = msg.get("content", "")
|
|
90
|
+
if isinstance(content, str):
|
|
91
|
+
all_text.append(content)
|
|
92
|
+
elif isinstance(content, list):
|
|
93
|
+
for block in content:
|
|
94
|
+
if isinstance(block, dict) and "text" in block:
|
|
95
|
+
all_text.append(block["text"])
|
|
96
|
+
|
|
97
|
+
combined = "\n".join(all_text)
|
|
98
|
+
return extract_facts_from_text(combined)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def facts_to_rules_markdown(facts: list[dict]) -> str:
|
|
102
|
+
"""Convert extracted facts to a markdown rules document."""
|
|
103
|
+
if not facts:
|
|
104
|
+
return ""
|
|
105
|
+
|
|
106
|
+
grouped: dict[str, list[dict]] = {}
|
|
107
|
+
for f in facts:
|
|
108
|
+
grouped.setdefault(f["type"], []).append(f)
|
|
109
|
+
|
|
110
|
+
lines = [
|
|
111
|
+
"# Local Environment Rules",
|
|
112
|
+
"",
|
|
113
|
+
"*Auto-generated from session history. Do not edit manually.*",
|
|
114
|
+
"",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
section_titles = {
|
|
118
|
+
"ssh_host": "SSH Hosts",
|
|
119
|
+
"ip_address": "Known Servers",
|
|
120
|
+
"data_path": "Data Paths",
|
|
121
|
+
"conda_env": "Python Environments",
|
|
122
|
+
"python_env": "Python Versions",
|
|
123
|
+
"api_endpoint": "API Endpoints",
|
|
124
|
+
"env_var": "Environment Variables",
|
|
125
|
+
"git_remote": "Git Repositories",
|
|
126
|
+
"ray_cluster": "Ray Cluster Config",
|
|
127
|
+
"cron_schedule": "Scheduled Jobs",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for fact_type, items in grouped.items():
|
|
131
|
+
title = section_titles.get(fact_type, fact_type.replace("_", " ").title())
|
|
132
|
+
lines.append(f"## {title}")
|
|
133
|
+
lines.append("")
|
|
134
|
+
for item in items:
|
|
135
|
+
lines.append(f"- `{item['value']}`")
|
|
136
|
+
lines.append("")
|
|
137
|
+
|
|
138
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Local rules file management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_RULES_DIR = Path("~/.openharness/local_rules").expanduser()
|
|
10
|
+
_RULES_FILE = _RULES_DIR / "rules.md"
|
|
11
|
+
_FACTS_FILE = _RULES_DIR / "facts.json"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _ensure_dir() -> None:
|
|
15
|
+
_RULES_DIR.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_local_rules() -> str:
|
|
19
|
+
"""Load the local rules markdown, or empty string if none exist."""
|
|
20
|
+
if _RULES_FILE.exists():
|
|
21
|
+
return _RULES_FILE.read_text(encoding="utf-8").strip()
|
|
22
|
+
return ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def save_local_rules(content: str) -> Path:
|
|
26
|
+
"""Write local rules markdown."""
|
|
27
|
+
_ensure_dir()
|
|
28
|
+
_RULES_FILE.write_text(content.strip() + "\n", encoding="utf-8")
|
|
29
|
+
return _RULES_FILE
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_facts() -> dict:
|
|
33
|
+
"""Load extracted facts as a dict."""
|
|
34
|
+
if _FACTS_FILE.exists():
|
|
35
|
+
return json.loads(_FACTS_FILE.read_text(encoding="utf-8"))
|
|
36
|
+
return {"facts": [], "last_updated": None}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def save_facts(facts: dict) -> None:
|
|
40
|
+
"""Persist extracted facts."""
|
|
41
|
+
_ensure_dir()
|
|
42
|
+
facts["last_updated"] = datetime.now(timezone.utc).isoformat()
|
|
43
|
+
_FACTS_FILE.write_text(
|
|
44
|
+
json.dumps(facts, indent=2, ensure_ascii=False) + "\n",
|
|
45
|
+
encoding="utf-8",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def merge_facts(existing: dict, new_facts: list[dict]) -> dict:
|
|
50
|
+
"""Merge new facts into existing, deduplicating by key."""
|
|
51
|
+
by_key = {}
|
|
52
|
+
for f in existing.get("facts", []):
|
|
53
|
+
by_key[f["key"]] = f
|
|
54
|
+
for f in new_facts:
|
|
55
|
+
key = f.get("key", "")
|
|
56
|
+
if key:
|
|
57
|
+
if key in by_key:
|
|
58
|
+
# Update with newer value, keep higher confidence
|
|
59
|
+
old = by_key[key]
|
|
60
|
+
if f.get("confidence", 0) >= old.get("confidence", 0):
|
|
61
|
+
by_key[key] = f
|
|
62
|
+
else:
|
|
63
|
+
by_key[key] = f
|
|
64
|
+
return {"facts": list(by_key.values())}
|