feat: add AgentHooks support for dogfooding#1131
feat: add AgentHooks support for dogfooding#1131IndenScale wants to merge 33 commits intoMoonshotAI:mainfrom
Conversation
🔧 Devin Review 修复已完成已根据 Devin Review 的反馈修复了 4 个问题: 修复摘要
详细说明
所有修复均已验证通过。 🔧 Devin Review Fix completed4 issues have been fixed based on feedback from Devin Review: Fix summary
Detailed description
All fixes have been verified. |
🔧 Devin Review 第二轮修复已完成已根据第二轮反馈修复了 3 个新问题: 修复摘要
详细说明
请重新扫描确认所有问题已修复。 🔧 Devin Review Round 2 Fix CompletedFixed 3 new issues from the second round of review:
Please rescan to verify all issues are resolved. |
🔄 更新:钩子触发器命名标准化本次推送对钩子事件命名进行了系统性重构,采用统一的 pre/post-event 模式: 命名变更对照表
主要改动
影响范围
所有旧命名仍然可用,无需修改现有钩子配置。 |
📦 agenthooks 参考实现已同步更新独立仓库(https://github.com/IndenScale/agenthooks)也已推送同步更新:
提交: |
🔧 Devin Review 第三轮修复已完成已根据最新反馈修复了 4 个问题: 修复摘要
详细说明
Commit: 所有相关测试通过 (71 passed)。 🔧 Devin Review The third round of repairs has been completed4 issues have been fixed based on latest feedback: Fix summary
Detailed description
Commit: All relevant tests passed (71 passed). |
…-0001) Add hooks system core architecture: - HooksConfig: Pydantic models for configuration - HookManager: registration, matching, and execution - Command hook execution engine with stdin/stdout protocol - Config integration: hooks field in main Config - Unit tests for config, models, and manager - Example configs: security.toml and productivity.toml Features: - 9 event types: session_start/end, before/after_agent, before/after_tool, after_tool_failure, subagent_start/stop, pre_compact - Matcher system with regex support for tool names and argument patterns - Exit code semantics: 0=success, 2=block, other=warning - Async execution with timeout control and fail-open policy - Environment variables: KIMI_SESSION_ID, KIMI_WORK_DIR, KIMI_ENV_FILE
Fix YAML frontmatter for EPIC-0001 and FEAT-0001~0004: - Add proper frontmatter with uid, created_at, updated_at - Move parent and tags into frontmatter - Fix dependency references - Add files field for tracking All issues now pass 'monoco issue lint' checks.
- Add before_tool hook execution in KimiToolset.handle() - Add after_tool hook execution for post-processing - Implement hook result caching for performance optimization - Add hook execution statistics tracking - Support blocking tool execution with deny decision - Support fail-open behavior on hook errors - Add comprehensive unit tests
- 添加 Prompt 类型钩子,支持 LLM 智能决策 - 添加 Agent 类型钩子,支持子 Agent 复杂验证 - 实现 --debug-hooks 参数和调试日志 - 编写中英文文档和 5+ 示例 hooks - 所有现有测试通过
Remove Prompt and Agent hook types to follow Gemini CLI's design philosophy: - Remove PromptHookConfig and AgentHookConfig classes - Remove PromptHookExecutor and AgentHookExecutor - Simplify HookConfig to single Command type - Update all tests to use unified HookConfig - Update documentation and examples - Fix code style issues (line length, unused imports) Hooks now only support Command type for transparency and simplicity. Users can implement LLM-based logic in external scripts if needed.
- 简化 hooks 系统为纯命令类型 - 重构 hooks 模块结构(discovery, executor, parser) - 更新配置和集成点 - 更新文档和示例
- 移除 Issues/ 和 .monoco/ 目录(保留本地文件) - 添加忽略规则防止意外提交
- 从 make test 改为 pytest tests/(跳过 e2e 和包测试) - timeout 从 120s 改为 30s - 避免每次 stop 都运行全量测试套件
- 添加 hook_manager 到 Runtime fixture - enforce-tests 只运行 core 和 utils 单元测试,排除 e2e 和工具测试 - 缩短 hook 超时时间为 30s
…locking The enforce-tests hook was running pytest on every session stop, causing significant delays and blocking the agent from completing. This change: - Removes actual test execution from enforce-tests hook - Hook now only checks for test directory existence - Always returns exit code 0 (allow) to avoid blocking - Updates timeout from 30s to 15s - Updates documentation to reflect new behavior Tests should be run in CI rather than blocking the agent's pre-stop hook.
Fix 4 issues reported by Devin Review: 1. toolset.py: Add default=str to json.dumps for non-serializable inputs - Prevents TypeError when tool_input contains bytes or custom objects 2. toolset.py: Fix stale tool_call_id in cached ToolResult - Cache only block_reason instead of full ToolResult - Construct new ToolResult with correct tool_call_id on cache hit 3. parser.py: Fix Matcher incorrectly matching None tool_name - Return False when tool_pattern is set but tool_name is None - Same fix for arg_pattern and arguments 4. kimisoul.py: Add limit to before_stop hook blocks - Prevent unbounded LLM calls when hook always blocks - Limit set to 3 consecutive blocks with warning log
- executor.py: use create_subprocess_exec instead of create_subprocess_shell to prevent hook scripts from silently failing on paths with spaces (security hooks could be bypassed due to fail-open policy) - kimisoul.py: add increment_step_count() call in _step() to ensure session_end hooks receive accurate total_steps data - toolset.py: remove ToolHookCache entirely as it was not part of AgentHooks specification and could cause false positives to be permanently cached without recovery mechanism
Replace the old config.toml-based hooks documentation with the new modular Agent Hooks standard: - Modular directory structure with HOOK.md + scripts/ - User-level (~/.config/agents/hooks/) and project-level (.agents/hooks/) layered configuration - Open standard compatible across agent platforms - Updated examples for HOOK.md frontmatter and executable scripts Both zh and en documentation are updated.
…attern Systematically rename all hook event types to follow the consistent pre/post-event naming convention: - session_start → pre-session - session_end → post-session - before_agent → pre-agent-turn - after_agent → post-agent-turn - before_tool → pre-tool-call - after_tool → post-tool-call - after_tool_failure → post-tool-call-failure - subagent_start → pre-subagent - subagent_stop → post-subagent - pre_compact → pre-context-compact - before_stop → pre-agent-turn-stop (NEW) - after_stop → post-agent-turn-stop (NEW) Changes: - Update HookEventType enum with canonical names and legacy aliases - Add normalize_trigger() for backward compatibility - Update all hook definitions and documentation - Add fire_and_forget() for async hook execution - Refactor hook manager to remove asyncio task tracking - Update test suite with new event names - Add tts-notification hook example All legacy names remain as aliases for backward compatibility.
- Remove TRIGGER_ALIASES mapping (legacy names -> canonical names) - Remove normalize_trigger() function - Remove redundant backwards compatibility alias in manager.py - Update VALID_TRIGGERS to only include canonical names - Update discovery.py to use direct trigger comparison
Add /hooks command to list and show information about loaded AgentHooks: - Show hook directories (user-level and project-level) - List all discovered hooks grouped by source - Display hook metadata (trigger, mode, priority, matcher) - Show execution statistics for current session - Provide instructions for managing hooks Extract display logic into new display.py module to keep slash.py clean.
The global-agents-md hook now properly passes content via stdout instead of writing to the project's AGENTS.md file.
- Fix Message.content JSON serialization for Pydantic models - Fix post-tool-call-failure hooks never firing (was hardcoded to post-tool-call) - Fix before_stop hooks overriding explicit user tool rejection - Fix zombie processes by awaiting proc.wait() after kill on timeout
- Remove .agents/hooks/.logs/session.log from git tracking - Add ignore rules for agent hooks runtime directories (.logs/, state/, cache/, tmp/)
- Fix hook context accumulation on continued sessions (app.py) - Fix subprocess resource leak on non-timeout exceptions (executor.py) - Add exception handling for _execute_pre_session_hooks (agent.py) - Add exception handling for execute_post_session_hooks (agent.py) All 67 hook tests pass.
e7badc8 to
61e187a
Compare
| if self.arg_pattern is not None: | ||
| if arguments is None or not self.arg_pattern.search(str(arguments)): | ||
| return False |
There was a problem hiding this comment.
🔴 Matcher pattern matching on str(dict) breaks anchored regex patterns like \.py$
The Matcher.matches() method converts the entire arguments dict to a string via str() before applying re.search(). This means str({"file_path": "/tmp/test.py", "content": "..."}) produces "{'file_path': '/tmp/test.py', 'content': '...'}"—a string ending with }, not .py. Anchored patterns such as \.py$ will therefore never match.
Root Cause and Impact
The Matcher is used in src/kimi_cli/hooks/manager.py:176 to pre-filter which hooks fire for a given event. The auto-format-python hook at .agents/hooks/auto-format-python/HOOK.md:7 specifies pattern: "\\.py$", intending to match only Python files. But because Matcher.matches() at src/kimi_cli/hooks/parser.py:199 does:
if arguments is None or not self.arg_pattern.search(str(arguments)):
return Falsethe $ anchor requires the entire dict string to end with .py, which it never will (it ends with }). This silently disables any hook whose matcher uses anchored patterns ($, ^). Specifically, auto-format-python will never fire for Python files. Non-anchored patterns (e.g., rm -rf /|mkfs) are unaffected because re.search finds them anywhere in the string.
Impact: Hooks relying on anchored regex patterns in matcher.pattern are silently broken and will never be executed.
Prompt for agents
Fix the Matcher.matches() method in src/kimi_cli/hooks/parser.py lines 192-202 so that the arg_pattern is tested against individual string values of the arguments dict (not the str() of the entire dict). For example, when arguments is a dict, iterate over the values and apply re.search to each string value. If any value matches, return True for the pattern check. This ensures anchored patterns like '\.py$' work correctly when matching against individual values like file paths. If arguments is not a dict, fall back to str(arguments) as currently done.
Was this helpful? React with 👍 or 👎 to provide feedback.
This PR adds AgentHooks support to kimi-cli for dogfooding purposes.
Changes
block-dangerous-commands: Security hook blocking dangerous shell commandsenforce-tests: Quality gate (checks test existence)auto-format-python: Auto-format Python files after writesession-logger/session-logger-end: Session auditing hookspre-session,post-session,pre-agent-turn,post-agent-turn,pre-agent-turn-stop,pre-tool-call,post-tool-call, etc./hooksslash command to display loaded hooks and their statisticsNotes
enforce-testshook checks for test directory existence (doesn't run tests)~/.config/kimi/hooks/) or project level (.agents/hooks/)