feat(ch12): phase 6 — hook event emission stream - #36
Merged
Conversation
ericleepi314
force-pushed
the
feat/ch12/phase6
branch
from
May 9, 2026 06:06
dfd1180 to
a3f0bc5
Compare
Phase 6 of ch12 extensibility refactor (plan §10). Closes gap #9 (no observability of hook execution for UI/SDK/telemetry subscribers). WI-6.1 — Hook event emission stream. New ``src/hooks/events.py`` module + emission seams in ``_run_hooks_for_event``. Mirrors TS ``typescript/src/utils/hooks/hookEvents.ts``. Three event types fire per hook execution: * ``hook_started`` — before each hook executes (subprocess/HTTP/LLM call). Carries hook_id, event, hook_type, command, source, tool_use_id. Lets subscribers show "hook running" spinners. * ``hook_response`` — after each hook returns. Carries the per-hook decision: exit_code, duration_ms, blocking_error, permission_behavior, command. NOT the aggregated decision — subscribers wanting that listen for hook_aggregated. * ``hook_aggregated`` — once per ``_run_hooks_for_event`` invocation, AFTER Phase-4 aggregation. Carries the AggregatedHookResult so subscribers see the final decision + full ``contributing_reasons`` attribution without re-deriving from individual responses. Skipped when no hooks fired. Subscriber API (mirrors hookEvents.ts): * ``register_hook_event_handler(handler) -> deregister_fn``: returns an idempotent deregister callable (calling twice is a no-op, matching the context-manager-cleanup pattern subscribers use). * ``set_all_hook_events_enabled(bool)``: global enable flag for runtime configuration / performance-sensitive code paths. * ``clear_hook_event_state()``: reset between tests. Concurrency: subscriber list guarded by ``threading.Lock``. Dispatch iterates a snapshot so a handler can deregister itself or others mid-iteration without racing the iteration. Error isolation contract (chapter requirement): a subscriber that raises must NOT break the executor or other subscribers. Each handler is called inside try/except; failures logged at WARNING (not exception — avoids massive tracebacks for buggy subscribers). Tested explicitly: * ``test_handler_exception_does_not_break_dispatch``: 2 handlers, first crashes, second still receives the event. * ``test_handler_exception_isolated_per_event``: 3 successive emits with a crashing handler; all 3 dispatches complete. * ``test_subscriber_crash_does_not_break_executor``: end-to-end — register a crashing handler, drive ``_run_hooks_for_event``, executor still completes and yields its messages. Tests — 14 new in tests/test_hook_event_emission.py: * Subscription: each event type round-trips; deregister stops delivery; idempotent unregister. * Exception isolation (3 tests, see above). * Global enable flag: suppress + re-enable. * clear_hook_event_state. * End-to-end via ``_run_hooks_for_event``: full event sequence with ordering pinned (started < response < aggregated); aggregated payload carries the AggregatedHookResult; no events when no hooks fire; subscriber crashes don't break the executor. Verification (S3 baseline-diff discipline): Phase 5 (5fa75d0 + 317aed2) failures saved at /tmp/phase5_failures.txt Phase 6 (this commit) failures saved at /tmp/phase6_failures.txt diff returns empty → identical 27-failure set, matching Phase 0 baseline (62a307c). Test counts: 3859 passed, 27 failed (Phase 5 was 3851 with the follow-up commit; +14 new in this commit). Hook+skill surface: 331/331 (Phase 5 was 317; +14). Local-import pattern (``from src.hooks.events import emit_hook_*`` inside the executor body) keeps the module dependency lazy — emitter costs are only paid when the surrounding code path is exercised. Zero-subscriber dispatch is a single ``_enabled`` short-circuit, so the overhead for sessions without listeners is negligible. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ericleepi314
force-pushed
the
feat/ch12/phase6
branch
from
May 9, 2026 06:13
a3f0bc5 to
4b523a0
Compare
ericleepi314
pushed a commit
that referenced
this pull request
Jul 7, 2026
feat(ch12): phase 6 — hook event emission stream
peroxider
pushed a commit
to peroxider/clawcodex
that referenced
this pull request
Jul 20, 2026
feat(orchestrator): F-118 task decomposition and F-124 issue clarifier MVP Created-by: qq_49552963 Commit-by: wukong;yeyunyao Merged-by: chadwweng Description: ## 这次做了什么 ### F-124:信息不够就先问,不再直接猜 - Issue 明确写了“细节未定 / 不要猜”时,先暂停派发并向作者追问。 - 只接受作者在追问之后的新回复;GitCode 评论按时间重新排序,避免把新回复当旧评论跳过。 - 机器人评论带隐藏 marker,即使机器人账号和 issue 作者是同一个,也不会把自己的追问当成作者回答。 - POST 没返回 comment ID 时不再靠相同正文猜游标;拿不到可靠游标就保持阻塞。 - GitCode token 改走 `Authorization: Bearer`,不再出现在 URL 和访问日志里。 ### F-118:真的拆任务、派 worker,并检查证据 - complex issue 会生成 task graph 和 wave;`mode:swarm` 会启动 coordinator。 - headless 现在不仅过滤工具,还会注入 coordinator 角色提示和 worker 能力上下文,因此模型会调用 `Agent`,不会反复撞不存在的 `Bash/Write`。 - 每个 task 由 write-capable worker 写独立的 `.orchestrator_control/task_evidence/task-N.json`,并行任务不会争写同一个文件。 - validator 检查状态、非空证据和依赖顺序;执行顺序使用 h144 文件系统记录的 evidence mtime,不信任模型自报的时间。 - swarm 未闭环时不会被 workspace-idle 早停抢先判完成。 ### 长工具调用不会再被误杀 - 内层 turn timeout 看到 `tool_use` 尚未对应 `tool_result` 时暂停。 - 外层 stream-stall watchdog 复用同一份 pending-tool 状态,不再把 worker 正在运行误判成 provider 卡死。 - 普通模型静默、单工具超时和总运行预算仍然保留。 ## click 仓实测 ### F-124:先追问,再继续实现 - Issue:[click agentforce314#30](https://gitcode.com/qq_49552963/click/issues/30) - 实际 PR:[click !24](https://gitcode.com/qq_49552963/click/merge_requests/24) - 页面上能看到来源 issue、实际分支、commit 和 `Verification: passed`。  ### F-118:四步 swarm 从 issue 跑到 PR - Issue:[click agentforce314#36](https://gitcode.com/qq_49552963/click/issues/36) - 实际 PR:[click !31](https://gitcode.com/qq_49552963/click/merge_requests/31) - 实际生成 4 个 task / 4 个 wave、4 份 task evidence;最终 `tests/test_testing.py` 为 `44 passed, 1 skipped`。 - 为了主动压测误杀问题,h144 上把 turn timeout 临时降到 30 秒;运行中多次记录 timeout deferred,worker 继续工作,最后 session complete、validator 通过并创建 PR。  ## h144 验证 - 环境:openEuler Linux / aarch64,Python 3.11;没有使用 Windows pytest。 - 最终相关回归:`251 passed in 65.64s`。 - click agentforce314#36 实际任务:`44 passed, 1 skipped`,session complete,创建 click PR !31。 - 更大范围回归:`1649 passed, 2 skipped, 18 subtests passed`;另有 3 个与本 MR 无关的既有环境/隔离失败(Linux 登录名假设、临时 session 路径补丁、无测试仓库 regression-guard 期望)。 - Python 编译、`git diff --check`、GitCode push hooks 通过。h144 环境未安装 Ruff,因此不把 Ruff 写成已通过。 ## 仍然保留的边界 - task 的状态和测试说明仍由 worker 写入;orchestrator 用独立文件、host mtime 和 fail-closed validator 复核,不把模型一句“完成了”当结果。 - 总运行预算和 per-tool timeout 仍会终止真正卡死的任务;本次只暂停会误伤健康 in-flight 工具的 stall/turn timeout。 See merge request: chadwweng/clawcodex!86
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 6 of ch12 — hook event emission stream for SDK / TUI / telemetry consumers. Three event types, idempotent subscriber API, exception-isolated dispatch.
What's new in this phase (vs Phase 5)
src/hooks/events.pywith three event types:hook_started— pre-execution, per-hookhook_response— post-execution, per-hook (carries hook's own decision, NOT aggregated)hook_aggregated— once per_run_hooks_for_eventinvocation, carriesAggregatedHookResultwithcontributing_reasonsregister_hook_event_handler(handler)returns idempotent deregister closure.set_all_hook_events_enabled(False)short-circuit for sessions without listeners.threading.Lockguards subscriber list; dispatch iterates a snapshot so handlers can deregister mid-iteration safely._run_hooks_for_event:emit_hook_startedbefore each_execute_command_hookemit_hook_responseafter (per-hook decision)emit_hook_aggregatedonce at the end withAggregatedHookResultTest plan
🤖 Generated with Claude Code