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.
Files changed (205) hide show
  1. {sliceagent-0.1.8 → sliceagent-0.1.9}/CHANGELOG.md +21 -0
  2. {sliceagent-0.1.8 → sliceagent-0.1.9}/PKG-INFO +1 -1
  3. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/__init__.py +1 -1
  4. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/cli.py +16 -5
  5. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/llm.py +7 -3
  6. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/neocortex.py +4 -2
  7. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/tools.py +9 -3
  8. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bughunt_fixes.py +74 -0
  9. {sliceagent-0.1.8 → sliceagent-0.1.9}/.dockerignore +0 -0
  10. {sliceagent-0.1.8 → sliceagent-0.1.9}/.env.example +0 -0
  11. {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  14. {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/dependabot.yml +0 -0
  15. {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/workflows/ci.yml +0 -0
  16. {sliceagent-0.1.8 → sliceagent-0.1.9}/.github/workflows/publish.yml +0 -0
  17. {sliceagent-0.1.8 → sliceagent-0.1.9}/.gitignore +0 -0
  18. {sliceagent-0.1.8 → sliceagent-0.1.9}/CODE_OF_CONDUCT.md +0 -0
  19. {sliceagent-0.1.8 → sliceagent-0.1.9}/CONTRIBUTING.md +0 -0
  20. {sliceagent-0.1.8 → sliceagent-0.1.9}/Dockerfile +0 -0
  21. {sliceagent-0.1.8 → sliceagent-0.1.9}/LICENSE +0 -0
  22. {sliceagent-0.1.8 → sliceagent-0.1.9}/NOTICE +0 -0
  23. {sliceagent-0.1.8 → sliceagent-0.1.9}/QUICKSTART.md +0 -0
  24. {sliceagent-0.1.8 → sliceagent-0.1.9}/README.md +0 -0
  25. {sliceagent-0.1.8 → sliceagent-0.1.9}/SECURITY.md +0 -0
  26. {sliceagent-0.1.8 → sliceagent-0.1.9}/examples/plugins/hello/__init__.py +0 -0
  27. {sliceagent-0.1.8 → sliceagent-0.1.9}/examples/plugins/hello/plugin.toml +0 -0
  28. {sliceagent-0.1.8 → sliceagent-0.1.9}/install.sh +0 -0
  29. {sliceagent-0.1.8 → sliceagent-0.1.9}/pyproject.toml +0 -0
  30. {sliceagent-0.1.8 → sliceagent-0.1.9}/scripts/gen_config_reference.py +0 -0
  31. {sliceagent-0.1.8 → sliceagent-0.1.9}/scripts/run_tests.sh +0 -0
  32. {sliceagent-0.1.8 → sliceagent-0.1.9}/sliceagent.toml.example +0 -0
  33. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/__main__.py +0 -0
  34. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/access.py +0 -0
  35. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/agents.py +0 -0
  36. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/background_review.py +0 -0
  37. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/binsniff.py +0 -0
  38. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/clock.py +0 -0
  39. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/code_grep.py +0 -0
  40. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/code_index.py +0 -0
  41. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/config.py +0 -0
  42. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/context_overflow.py +0 -0
  43. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/envspec.py +0 -0
  44. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/errors.py +0 -0
  45. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/events.py +0 -0
  46. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/finding_types.py +0 -0
  47. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/flags.py +0 -0
  48. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/fuzzy.py +0 -0
  49. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/guardrails.py +0 -0
  50. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/guidance.py +0 -0
  51. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/hippocampus.py +0 -0
  52. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/hooks.py +0 -0
  53. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/interfaces.py +0 -0
  54. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/loop.py +0 -0
  55. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/mcp_client.py +0 -0
  56. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/mcp_security.py +0 -0
  57. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/memory.py +0 -0
  58. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/metrics.py +0 -0
  59. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/model_catalog.py +0 -0
  60. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/monitor.py +0 -0
  61. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/onboarding.py +0 -0
  62. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/oracle.py +0 -0
  63. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/pagetable.py +0 -0
  64. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/pfc.py +0 -0
  65. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/plugins.py +0 -0
  66. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/policy.py +0 -0
  67. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/procman.py +0 -0
  68. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/prompt.py +0 -0
  69. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/records.py +0 -0
  70. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/recovery.py +0 -0
  71. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/regions.py +0 -0
  72. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/registry.py +0 -0
  73. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/retriever.py +0 -0
  74. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/safety.py +0 -0
  75. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/sandbox.py +0 -0
  76. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/scheduler.py +0 -0
  77. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/search_index.py +0 -0
  78. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/seed.py +0 -0
  79. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/sensory_cortex.py +0 -0
  80. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/session.py +0 -0
  81. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/skill_provenance.py +0 -0
  82. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/skill_usage.py +0 -0
  83. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/skills.py +0 -0
  84. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/subagent.py +0 -0
  85. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/subdir_hints.py +0 -0
  86. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/swap.py +0 -0
  87. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/taskstate.py +0 -0
  88. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/telemetry.py +0 -0
  89. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/terminal.py +0 -0
  90. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/text_utils.py +0 -0
  91. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/tool_summary.py +0 -0
  92. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/tui.py +0 -0
  93. {sliceagent-0.1.8 → sliceagent-0.1.9}/src/sliceagent/web.py +0 -0
  94. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/README.md +0 -0
  95. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_active_focus.py +0 -0
  96. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_agents.py +0 -0
  97. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_ask_echo.py +0 -0
  98. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_ask_user.py +0 -0
  99. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_background_review.py +0 -0
  100. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_binary_view.py +0 -0
  101. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bound_is_relevance.py +0 -0
  102. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_breadth_e2e.py +0 -0
  103. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_2b.py +0 -0
  104. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_breadth_wave.py +0 -0
  105. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_current_request.py +0 -0
  106. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_linenum.py +0 -0
  107. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_loop_wave.py +0 -0
  108. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_memory_wave.py +0 -0
  109. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_p0a.py +0 -0
  110. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_p0b.py +0 -0
  111. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_p0c.py +0 -0
  112. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_selfreview.py +0 -0
  113. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_selfreview_siblings.py +0 -0
  114. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_skills_h1.py +0 -0
  115. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_slice_wave.py +0 -0
  116. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_subagent_wave.py +0 -0
  117. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_bugfix_tools_wave.py +0 -0
  118. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_cache_manifest.py +0 -0
  119. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_checkpoint.py +0 -0
  120. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_cli_smoke.py +0 -0
  121. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_closure.py +0 -0
  122. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_code_grep.py +0 -0
  123. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_code_index.py +0 -0
  124. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_config_journey.py +0 -0
  125. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_consolidate.py +0 -0
  126. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_context_overflow.py +0 -0
  127. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_conversation.py +0 -0
  128. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_coresidency.py +0 -0
  129. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_echo_before_blocking.py +0 -0
  130. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_episode.py +0 -0
  131. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_errors_backoff.py +0 -0
  132. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_esc_sentinel.py +0 -0
  133. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_exec_env.py +0 -0
  134. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_finding_types.py +0 -0
  135. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_flags.py +0 -0
  136. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_fuzzy.py +0 -0
  137. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_ghost_index.py +0 -0
  138. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_guardrails.py +0 -0
  139. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_guidance.py +0 -0
  140. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_history.py +0 -0
  141. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_invariant_fixes.py +0 -0
  142. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_live_composer.py +0 -0
  143. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_llm_streaming.py +0 -0
  144. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_llm_usage_cache.py +0 -0
  145. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_llm_watchdog.py +0 -0
  146. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_log_redaction.py +0 -0
  147. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_loop_overflow.py +0 -0
  148. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mcp_output_cap.py +0 -0
  149. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mcp_runtime.py +0 -0
  150. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_memory.py +0 -0
  151. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_memory_persist.py +0 -0
  152. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_menu_select.py +0 -0
  153. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_metrics.py +0 -0
  154. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mining.py +0 -0
  155. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_mission_tier.py +0 -0
  156. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_model_catalog.py +0 -0
  157. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_monitor.py +0 -0
  158. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_onboarding.py +0 -0
  159. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_pageout_compaction.py +0 -0
  160. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_permission_patterns.py +0 -0
  161. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_pinned_composer.py +0 -0
  162. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_plan_tier.py +0 -0
  163. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_procman.py +0 -0
  164. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_product_features.py +0 -0
  165. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_progress_status.py +0 -0
  166. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_prompt_ab.py +0 -0
  167. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_provenance.py +0 -0
  168. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_provider_lineup.py +0 -0
  169. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_proxy_interrupt.py +0 -0
  170. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_reach.py +0 -0
  171. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_readonly_subagent.py +0 -0
  172. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_recall_search.py +0 -0
  173. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_records.py +0 -0
  174. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_refault.py +0 -0
  175. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_registry_validation.py +0 -0
  176. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_reliability.py +0 -0
  177. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_repo_map_bounded.py +0 -0
  178. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_requirements.py +0 -0
  179. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_review_fixes.py +0 -0
  180. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_route_lexical.py +0 -0
  181. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_safety.py +0 -0
  182. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_seal_markdown.py +0 -0
  183. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_search_index.py +0 -0
  184. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_skill_meta.py +0 -0
  185. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_skill_metadata.py +0 -0
  186. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_subdir_hints.py +0 -0
  187. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_task_agnostic.py +0 -0
  188. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_task_state_roundtrip.py +0 -0
  189. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_telemetry_convergence.py +0 -0
  190. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_terminal.py +0 -0
  191. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tool_dedup.py +0 -0
  192. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tool_result_ok.py +0 -0
  193. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tools_robust.py +0 -0
  194. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_topic_switch.py +0 -0
  195. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_trajectory.py +0 -0
  196. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui.py +0 -0
  197. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui_menus.py +0 -0
  198. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui_render.py +0 -0
  199. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_tui_widgets.py +0 -0
  200. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_turn_budget.py +0 -0
  201. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_verification_agent.py +0 -0
  202. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_web.py +0 -0
  203. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_workspace.py +0 -0
  204. {sliceagent-0.1.8 → sliceagent-0.1.9}/tests/test_world_region.py +0 -0
  205. {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.8
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
@@ -1,3 +1,3 @@
1
1
  """sliceagent — a memory-native coding agent (deterministic, bounded, reconstructed context)."""
2
2
 
3
- __version__ = "0.1.8"
3
+ __version__ = "0.1.9"
@@ -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
- return {k: (redact_text(v) if isinstance(v, str) else v) for k, v in (args or {}).items()}
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
- _console.print(" " + base_tools.undo_last()) # revert the last file edit
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
- _console.print(f" plugin dirs: {', '.join(cfg.plugin_dirs) or '(none configured)'}")
574
- _console.print(f" plugin tools ({len(tools)}): {', '.join(tools) or '(none loaded)'}")
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
- http_client = httpx.Client(proxy=proxy, timeout=timeout) if use_proxy else httpx.Client(timeout=timeout)
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 uses Windows CRLF line endings (sample the head). Used to PRESERVE
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
- return b"\r\n" in f.read(65536)
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