sliceagent 0.1.8__tar.gz → 0.1.9__tar.gz
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.
- {sliceagent-0.1.8 → sliceagent-0.1.9}/CHANGELOG.md +21 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/PKG-INFO +1 -1
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/__init__.py +1 -1
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/cli.py +16 -5
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/llm.py +7 -3
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/neocortex.py +4 -2
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/tools.py +9 -3
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bughunt_fixes.py +74 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.dockerignore +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.env.example +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/dependabot.yml +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/workflows/ci.yml +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/workflows/publish.yml +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/.gitignore +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/CODE_OF_CONDUCT.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/CONTRIBUTING.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/Dockerfile +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/LICENSE +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/NOTICE +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/QUICKSTART.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/README.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/SECURITY.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/examples/plugins/hello/__init__.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/examples/plugins/hello/plugin.toml +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/install.sh +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/pyproject.toml +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/scripts/gen_config_reference.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/scripts/run_tests.sh +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/sliceagent.toml.example +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/__main__.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/access.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/agents.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/background_review.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/binsniff.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/clock.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/code_grep.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/code_index.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/config.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/context_overflow.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/envspec.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/errors.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/events.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/finding_types.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/flags.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/fuzzy.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/guardrails.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/guidance.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/hippocampus.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/hooks.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/interfaces.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/loop.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/mcp_client.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/mcp_security.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/memory.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/metrics.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/model_catalog.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/monitor.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/onboarding.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/oracle.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/pagetable.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/pfc.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/plugins.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/policy.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/procman.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/prompt.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/records.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/recovery.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/regions.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/registry.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/retriever.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/safety.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/sandbox.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/scheduler.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/search_index.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/seed.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/sensory_cortex.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/session.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/skill_provenance.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/skill_usage.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/skills.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/subagent.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/subdir_hints.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/swap.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/taskstate.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/telemetry.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/terminal.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/text_utils.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/tool_summary.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/tui.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/web.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/README.md +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_active_focus.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_agents.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_ask_echo.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_ask_user.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_background_review.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_binary_view.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bound_is_relevance.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_breadth_e2e.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_2b.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_breadth_wave.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_current_request.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_linenum.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_loop_wave.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_memory_wave.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_p0a.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_p0b.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_p0c.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_selfreview.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_selfreview_siblings.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_skills_h1.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_slice_wave.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_subagent_wave.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_tools_wave.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_cache_manifest.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_checkpoint.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_cli_smoke.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_closure.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_code_grep.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_code_index.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_config_journey.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_consolidate.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_context_overflow.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_conversation.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_coresidency.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_echo_before_blocking.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_episode.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_errors_backoff.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_esc_sentinel.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_exec_env.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_finding_types.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_flags.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_fuzzy.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_ghost_index.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_guardrails.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_guidance.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_history.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_invariant_fixes.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_live_composer.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_llm_streaming.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_llm_usage_cache.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_llm_watchdog.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_log_redaction.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_loop_overflow.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mcp_output_cap.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mcp_runtime.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_memory.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_memory_persist.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_menu_select.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_metrics.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mining.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mission_tier.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_model_catalog.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_monitor.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_onboarding.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_pageout_compaction.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_permission_patterns.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_pinned_composer.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_plan_tier.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_procman.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_product_features.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_progress_status.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_prompt_ab.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_provenance.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_provider_lineup.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_proxy_interrupt.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_reach.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_readonly_subagent.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_recall_search.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_records.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_refault.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_registry_validation.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_reliability.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_repo_map_bounded.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_requirements.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_review_fixes.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_route_lexical.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_safety.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_seal_markdown.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_search_index.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_skill_meta.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_skill_metadata.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_subdir_hints.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_task_agnostic.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_task_state_roundtrip.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_telemetry_convergence.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_terminal.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tool_dedup.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tool_result_ok.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tools_robust.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_topic_switch.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_trajectory.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui_menus.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui_render.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui_widgets.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_turn_budget.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_verification_agent.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_web.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_workspace.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_world_region.py +0 -0
- {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_world_state.py +0 -0
|
@@ -5,6 +5,27 @@ this project aims for [Semantic Versioning](https://semver.org/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.1.9] — 2026-07-03
|
|
9
|
+
|
|
10
|
+
Bug-hunt round 2 (deep-core lenses, full 3-vote adversarial verify). Five confirmed fixes.
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
- The durable debug log's `_scrub_args` now redacts secrets in NESTED dict/list tool arguments
|
|
14
|
+
(e.g. an MCP call's `{config:{api_key:…}}` or `{headers:{Authorization:…}}`) — top-level-only
|
|
15
|
+
redaction leaked them to `~/.sliceagent/logs/**/durable-log.jsonl` in plaintext.
|
|
16
|
+
- `AGENT_PROXY=none` (documented "force a DIRECT connection") now truly forces direct: the httpx
|
|
17
|
+
client is built with `trust_env=False`, so an ambient `HTTPS_PROXY` can no longer silently route
|
|
18
|
+
your API traffic through a proxy you told sliceagent to bypass (both first build and `/model` hops).
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `str_replace`/`edit_file` no longer flip a whole file to CRLF when it's mostly-LF but contains a
|
|
22
|
+
single embedded `\r\n` (a byte literal, an HTTP fixture, a merge artifact). CRLF is now detected by
|
|
23
|
+
DOMINANCE, not mere presence — uniformly-CRLF Windows files are still preserved.
|
|
24
|
+
- Session-end consolidation no longer crashes (and silently discards ALL of a session's promoted
|
|
25
|
+
lessons/skills) when the episodic log contains one malformed record.
|
|
26
|
+
- `/undo` and `/cwd` (and `/plugins`) no longer corrupt output or crash Rich with a MarkupError when a
|
|
27
|
+
path contains brackets — e.g. a Next.js `app/[id]/page.tsx` route or `~/proj/[slug]`.
|
|
28
|
+
|
|
8
29
|
## [0.1.8] — 2026-07-02
|
|
9
30
|
|
|
10
31
|
Launch-day bug-hunt round: a security fix plus config-robustness and /model correctness.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sliceagent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: A coding agent with a new context-engineering framework: bounded, deterministic, reconstructed context (slice architecture) — built for long-horizon work.
|
|
5
5
|
Author: TT-Wang
|
|
6
6
|
License-Expression: MIT
|
|
@@ -50,7 +50,15 @@ def log_sink(root: str = ".", path: str | None = None):
|
|
|
50
50
|
path = path or os.path.join(state_dir("logs", root_key(root)), "durable-log.jsonl")
|
|
51
51
|
|
|
52
52
|
def _scrub_args(args: dict) -> dict: # redact string values (edit_file content, inline tokens)
|
|
53
|
-
|
|
53
|
+
def _rec(v): # RECURSE — a secret nested in a dict/list arg (e.g. an
|
|
54
|
+
if isinstance(v, str): # MCP tool's {config:{api_key:…}} / {headers:{Authorization:…}})
|
|
55
|
+
return redact_text(v) # must not reach the on-disk log in plaintext (top-level-only
|
|
56
|
+
if isinstance(v, dict): # redaction leaked it; sibling hippocampus._clamp already recurses)
|
|
57
|
+
return {k: _rec(x) for k, x in v.items()}
|
|
58
|
+
if isinstance(v, (list, tuple)):
|
|
59
|
+
return [_rec(x) for x in v]
|
|
60
|
+
return v
|
|
61
|
+
return _rec(args or {})
|
|
54
62
|
|
|
55
63
|
def sink(e: Event) -> None:
|
|
56
64
|
rec = None
|
|
@@ -566,12 +574,15 @@ def main() -> None:
|
|
|
566
574
|
except Exception:
|
|
567
575
|
_console.print(f" no such topic: {arg}", markup=False)
|
|
568
576
|
elif cmd == "/undo":
|
|
569
|
-
|
|
577
|
+
# markup=False: undo_last() embeds the edited file PATH; a Next.js-style '[id]'/'[...slug]'
|
|
578
|
+
# segment is parsed as a Rich tag → corrupted output or a MarkupError crash.
|
|
579
|
+
_console.print(" " + base_tools.undo_last(), markup=False) # revert the last file edit
|
|
570
580
|
elif cmd == "/plugins":
|
|
571
581
|
tools = sorted(e.name for e in base_tools.registry._tools.values()
|
|
572
582
|
if getattr(e, "source", "") == "plugin")
|
|
573
|
-
|
|
574
|
-
_console.print(f" plugin
|
|
583
|
+
# markup=False: plugin dirs are filesystem PATHS that may contain '[...]' (same Rich-tag hazard)
|
|
584
|
+
_console.print(f" plugin dirs: {', '.join(cfg.plugin_dirs) or '(none configured)'}", markup=False)
|
|
585
|
+
_console.print(f" plugin tools ({len(tools)}): {', '.join(tools) or '(none loaded)'}", markup=False)
|
|
575
586
|
elif cmd == "/mcp":
|
|
576
587
|
configured = list(cfg.mcp_servers.keys())
|
|
577
588
|
mtools = sorted(e.name for e in base_tools.registry._tools.values()
|
|
@@ -670,7 +681,7 @@ def main() -> None:
|
|
|
670
681
|
if not arg:
|
|
671
682
|
_console.print(f" workspace: {base_tools.root()}", markup=False)
|
|
672
683
|
else:
|
|
673
|
-
_console.print(" ✓ " + _reroot(arg))
|
|
684
|
+
_console.print(" ✓ " + _reroot(arg), markup=False) # path may contain '[...]' → Rich markup
|
|
674
685
|
else:
|
|
675
686
|
_console.print(f" unknown command {cmd} (/help)", markup=False)
|
|
676
687
|
return True
|
|
@@ -208,7 +208,11 @@ class OpenAILLM:
|
|
|
208
208
|
or os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY"))
|
|
209
209
|
use_proxy = bool(proxy) and proxy != "none"
|
|
210
210
|
self.proxy_used = proxy if use_proxy else "direct" # exposed so the CLI can announce the route (A4)
|
|
211
|
-
|
|
211
|
+
# trust_env=False: the proxy is resolved EXPLICITLY above (AGENT_PROXY/HTTPS_PROXY/HTTP_PROXY →
|
|
212
|
+
# _choose_proxy), so httpx must NOT ALSO auto-read ambient proxy env — otherwise AGENT_PROXY=none
|
|
213
|
+
# ("force direct") still routes through an ambient HTTPS_PROXY (httpx defaults trust_env=True).
|
|
214
|
+
http_client = (httpx.Client(proxy=proxy, timeout=timeout, trust_env=False) if use_proxy
|
|
215
|
+
else httpx.Client(timeout=timeout, trust_env=False))
|
|
212
216
|
|
|
213
217
|
# Enforce the request timeout at the SDK layer too. The openai SDK applies its OWN per-request
|
|
214
218
|
# timeout (default ~600s) which OVERRIDES the httpx client's, so without passing it here a
|
|
@@ -278,8 +282,8 @@ class OpenAILLM:
|
|
|
278
282
|
use_proxy = bool(proxy) and proxy != "none"
|
|
279
283
|
self.proxy_used = proxy if use_proxy else "direct"
|
|
280
284
|
timeout = getattr(self, "_timeout", 60.0)
|
|
281
|
-
http_client = (httpx.Client(proxy=proxy, timeout=timeout) if use_proxy
|
|
282
|
-
else httpx.Client(timeout=timeout))
|
|
285
|
+
http_client = (httpx.Client(proxy=proxy, timeout=timeout, trust_env=False) if use_proxy
|
|
286
|
+
else httpx.Client(timeout=timeout, trust_env=False)) # explicit proxy only (see __init__)
|
|
283
287
|
ckw: dict = {"api_key": new_key}
|
|
284
288
|
if new_base:
|
|
285
289
|
ckw["base_url"] = new_base
|
|
@@ -87,6 +87,8 @@ def _is_secret(text: str) -> bool:
|
|
|
87
87
|
def _by_task(records: list[dict]) -> list[list[dict]]:
|
|
88
88
|
groups: dict[str, list[dict]] = {}
|
|
89
89
|
for r in records:
|
|
90
|
+
if not isinstance(r, dict): # a malformed episodic line (null/list/partial write, old schema)
|
|
91
|
+
continue # must not crash consolidation → silently DISCARD ALL lessons this session
|
|
90
92
|
groups.setdefault(r.get("task_id", ""), []).append(r)
|
|
91
93
|
return [sorted(g, key=lambda r: r.get("turn", 0)) for g in groups.values()]
|
|
92
94
|
|
|
@@ -98,7 +100,7 @@ def promote_episodes(records: list[dict]) -> list[dict]:
|
|
|
98
100
|
# pass 1: collect the corrective pitfall of each task + count signatures (frequency, #4)
|
|
99
101
|
cand = []
|
|
100
102
|
for recs in _by_task(records):
|
|
101
|
-
rmeta = [r.get("record", {}) for r in recs]
|
|
103
|
+
rmeta = [(r.get("record") if isinstance(r.get("record"), dict) else {}) for r in recs] # {"record": null} → {} (not None)
|
|
102
104
|
had_fail = any(m.get("meta", {}).get("failing") for m in rmeta)
|
|
103
105
|
last = rmeta[-1].get("meta", {})
|
|
104
106
|
if not (had_fail and last.get("stop_reason") == "end_turn" and not last.get("failing")):
|
|
@@ -222,7 +224,7 @@ def promote_procedures(records: list[dict], *, min_actions: int = PROC_MIN_ACTIO
|
|
|
222
224
|
EverOS rule), capped. Pure. Returns {kind, name, description, steps, files, freq, tags}."""
|
|
223
225
|
cand = []
|
|
224
226
|
for recs in _by_task(records):
|
|
225
|
-
rmeta = [r.get("record", {}) for r in recs]
|
|
227
|
+
rmeta = [(r.get("record") if isinstance(r.get("record"), dict) else {}) for r in recs] # {"record": null} → {} (not None)
|
|
226
228
|
if any(m.get("meta", {}).get("failing") for m in rmeta):
|
|
227
229
|
continue # corrective → a fact, not a procedure
|
|
228
230
|
last = rmeta[-1].get("meta", {})
|
|
@@ -747,14 +747,20 @@ class LocalToolHost:
|
|
|
747
747
|
|
|
748
748
|
@staticmethod
|
|
749
749
|
def _detect_crlf(full: str) -> bool:
|
|
750
|
-
"""True if the existing file
|
|
750
|
+
"""True if the existing file is DOMINANTLY Windows CRLF (sample the head). Used to PRESERVE
|
|
751
751
|
line endings on edit: the model emits '\\n', and writing that to a CRLF file rewrites every line
|
|
752
|
-
ending — a huge spurious diff / corruption on Windows-authored repos.
|
|
752
|
+
ending — a huge spurious diff / corruption on Windows-authored repos. DOMINANCE (not mere
|
|
753
|
+
presence): a mostly-LF file with one embedded '\\r\\n' (a byte literal, an HTTP fixture, a merge
|
|
754
|
+
artifact) must NOT be flipped whole-file to CRLF — while a uniformly-CRLF file with one stray LF
|
|
755
|
+
still counts as CRLF. crlf ≥ (bare-LF) covers both, and keeps the pinned uniform cases."""
|
|
753
756
|
try:
|
|
754
757
|
with open(full, "rb") as f:
|
|
755
|
-
|
|
758
|
+
head = f.read(65536)
|
|
756
759
|
except OSError:
|
|
757
760
|
return False
|
|
761
|
+
crlf = head.count(b"\r\n")
|
|
762
|
+
lf_only = head.count(b"\n") - crlf # LFs that are NOT part of a CRLF
|
|
763
|
+
return crlf > 0 and crlf >= lf_only
|
|
758
764
|
|
|
759
765
|
@staticmethod
|
|
760
766
|
def _preserve_eol(text: str, crlf: bool) -> str:
|
|
@@ -1347,6 +1347,80 @@ def model_switch_warns_when_the_endpoint_cant_serve_it():
|
|
|
1347
1347
|
assert "OpenAI" not in _reasoning_note(LLM("gpt-5.5", "", "high"))
|
|
1348
1348
|
|
|
1349
1349
|
|
|
1350
|
+
# ── Round 2 (2026-07-03) — deep-core hunt confirmed fixes ─────────────────────────────────────
|
|
1351
|
+
@check
|
|
1352
|
+
def durable_log_redacts_secrets_in_NESTED_tool_args():
|
|
1353
|
+
# HIGH: _scrub_args redacted only top-level string values → a secret in a nested dict/list arg
|
|
1354
|
+
# (an MCP tool's {config:{api_key:…}} / {headers:{Authorization:…}}) hit the on-disk log in plaintext.
|
|
1355
|
+
from sliceagent.cli import log_sink
|
|
1356
|
+
from sliceagent.events import ToolResult
|
|
1357
|
+
d = tempfile.mkdtemp(); path = os.path.join(d, "log.jsonl")
|
|
1358
|
+
sink = log_sink(root=d, path=path)
|
|
1359
|
+
secret = "sk-verylongsecretkey1234567890abcdefghij"
|
|
1360
|
+
tok = "ghp_1234567890abcdefghijklmnopqrstuvwx"
|
|
1361
|
+
sink(ToolResult("mcp_call", {"config": {"api_key": secret}, "items": [tok]}, "ok", False))
|
|
1362
|
+
body = open(path).read()
|
|
1363
|
+
assert secret not in body, "a secret in a NESTED dict arg must be redacted before the durable log"
|
|
1364
|
+
assert tok not in body, "a secret in a NESTED list arg must be redacted too"
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
@check
|
|
1368
|
+
def detect_crlf_is_dominance_not_mere_presence():
|
|
1369
|
+
from sliceagent.tools import LocalToolHost
|
|
1370
|
+
h = LocalToolHost(tempfile.mkdtemp())
|
|
1371
|
+
def probe(name, data):
|
|
1372
|
+
p = os.path.join(h.root(), name); open(p, "wb").write(data); return h._detect_crlf(p)
|
|
1373
|
+
assert probe("u_crlf", b"a\r\nb\r\nc\r\n") is True # uniform CRLF → CRLF (pinned)
|
|
1374
|
+
assert probe("u_lf", b"a\nb\nc\n") is False # uniform LF → LF (pinned)
|
|
1375
|
+
assert probe("mixed", b'x = b"h\r\n"\np = 8080\nd = 1\n') is False # mostly-LF + 1 CRLF → LF (the fix)
|
|
1376
|
+
assert probe("winstray", b"a\r\nb\r\nc\r\nd\n") is True # CRLF-dominant + 1 stray LF → CRLF
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
@check
|
|
1380
|
+
def consolidation_survives_a_malformed_episodic_record():
|
|
1381
|
+
from sliceagent.neocortex import promote_episodes, promote_procedures
|
|
1382
|
+
good = [{"task_id": "t", "turn": 1, "record": {"meta": {"failing": True}}},
|
|
1383
|
+
{"task_id": "t", "turn": 2, "record": {"meta": {"failing": False, "stop_reason": "end_turn"}}}]
|
|
1384
|
+
for bad in (None, [1, 2], "str", 42, {"record": None}): # each a malformed line among good ones
|
|
1385
|
+
promote_episodes(good + [bad]) # must not raise (was AttributeError → lost ALL lessons)
|
|
1386
|
+
promote_procedures(good + [bad])
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
@check
|
|
1390
|
+
def direct_llm_client_ignores_ambient_proxy_env():
|
|
1391
|
+
import sliceagent.llm as _llm
|
|
1392
|
+
saved = {k: os.environ.get(k) for k in ("AGENT_PROXY", "HTTPS_PROXY", "HTTP_PROXY", "LLM_API_KEY")}
|
|
1393
|
+
try:
|
|
1394
|
+
os.environ.update({"AGENT_PROXY": "none", "HTTPS_PROXY": "http://ambient:7890", "LLM_API_KEY": "k"})
|
|
1395
|
+
llm = _llm.OpenAILLM(model="deepseek-chat", base_url="https://api.deepseek.com/v1")
|
|
1396
|
+
assert llm.proxy_used == "direct"
|
|
1397
|
+
assert llm.client._client.trust_env is False, "AGENT_PROXY=none must truly force direct (no ambient proxy)"
|
|
1398
|
+
llm.switch(model="gpt-5.5", base_url="", api_key="k2")
|
|
1399
|
+
assert llm.client._client.trust_env is False, "the switch() direct client must ignore ambient proxy too"
|
|
1400
|
+
finally:
|
|
1401
|
+
for k, v in saved.items():
|
|
1402
|
+
os.environ.pop(k, None) if v is None else os.environ.__setitem__(k, v)
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
@check
|
|
1406
|
+
def slash_handlers_print_bracketed_paths_without_markup_crash():
|
|
1407
|
+
# /undo and /cwd echo a workspace PATH; a Next.js '[id]' segment is a Rich tag → MarkupError.
|
|
1408
|
+
from io import StringIO
|
|
1409
|
+
|
|
1410
|
+
from rich.console import Console
|
|
1411
|
+
con = Console(file=StringIO(), force_terminal=False)
|
|
1412
|
+
# a real /cwd echo: `_reroot("~/proj/[/]")` returns "not a directory: ~/proj/[/]" — '[/]' is a Rich
|
|
1413
|
+
# closing tag with nothing to close (the verifiers' exact repro).
|
|
1414
|
+
con.print(" ✓ not a directory: ~/proj/[/]", markup=False) # must not raise
|
|
1415
|
+
con.print(" Undid the last edit to app/[id]/page.tsx (1 change).", markup=False)
|
|
1416
|
+
raised = False
|
|
1417
|
+
try:
|
|
1418
|
+
con.print("not a directory: ~/proj/[/]") # markup ON → the MarkupError the fix avoids
|
|
1419
|
+
except Exception: # noqa: BLE001
|
|
1420
|
+
raised = True
|
|
1421
|
+
assert raised, "sanity: '[/]' DOES break Rich with markup on (so markup=False is load-bearing)"
|
|
1422
|
+
|
|
1423
|
+
|
|
1350
1424
|
def main():
|
|
1351
1425
|
failed = 0
|
|
1352
1426
|
for fn in CHECKS:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|