Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Bugfix: subagent `postRequest` hooks now include `parent_chat_id`, letting hooks distinguish primary and subagent completions. (#505)
- Bugfix: clearer rejected tool-call result wording so models no longer assume a rejected edit was applied; it now states the call did not run and changed nothing. (#507)
- Bugfix: `ask_user` normalizes the `options` argument so a malformed value (e.g. a stringified options an LLM sometimes emits) no longer reaches clients as broken choices. Accepts string/object arrays, recovers a JSON-encoded string, and drops unusable input.

Expand Down
2 changes: 1 addition & 1 deletion docs/config/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ Fires after a primary-agent prompt finishes. Also runs for subagents. Primary us
!!! note
`postRequest` fires only after LLM responses. Display-only commands (`/hooks`, `/model`, `/costs`) and compaction prompts (`/compact` and auto-compact) do **not** trigger it — use [`postCompact`](#postcompact) for compaction.

- **Input adds** — `response` (last assistant text) and `follow_up_active` (`true` when this turn was triggered by a previous `followUp`).
- **Input adds** — `response` (last assistant text), `follow_up_active` (`true` when this turn was triggered by a previous `followUp`), and, for subagents, `parent_chat_id`.
- **Honored output**:
- `followUp` — start a new LLM turn after this one (see [follow-up turns](#follow-up-turns)).
- `systemMessage`, `suppressOutput`.
Expand Down
7 changes: 5 additions & 2 deletions src/eca/features/chat/lifecycle.clj
Original file line number Diff line number Diff line change
Expand Up @@ -325,19 +325,22 @@
base-hook-data (assoc-some (f.hooks/chat-hook-data db chat-ctx)
:response response
:follow-up-active (boolean follow-up-active?))
post-request-hook-data (assoc-some base-hook-data
:parent-chat-id (when subagent?
(db/parent-chat-id db chat-id)))
cb {:on-before-action (partial notify-before-hook-action! chat-ctx)
:on-after-action (fn [result]
(notify-after-hook-action! chat-ctx result)
(swap! results* conj result))}
_ (f.hooks/trigger-if-matches! :postRequest base-hook-data cb db config)
_ (f.hooks/trigger-if-matches! :postRequest post-request-hook-data cb db config)
;; A successful continue:false on a postRequest hook stops the turn, so
;; the remaining relevant hooks must not run. For subagents this means
;; subagentPostRequest is skipped, otherwise it could emit side effects
;; (systemMessage, followUp) after the turn was already stopped.
post-request-stopped? (boolean (some f.hooks/successful-continue-false? @results*))
_ (when (and subagent? (not post-request-stopped?))
(f.hooks/trigger-if-matches! :subagentPostRequest
(assoc base-hook-data :parent-chat-id (db/parent-chat-id db chat-id))
post-request-hook-data
cb
db
config))
Expand Down
8 changes: 4 additions & 4 deletions test/eca/features/hooks_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1864,18 +1864,18 @@
:actions [{:type "shell" :shell "echo pr"}]}
"spr" {:type "subagentPostRequest"
:actions [{:type "shell" :shell "echo spr"}]}}})
(let [fired-types* (atom [])]
(let [payloads* (atom [])]
(with-redefs [f.hooks/run-shell-cmd (fn [{:keys [input]}]
(let [data (json/parse-string input true)]
(swap! fired-types* conj (:hook_type data)))
(swap! payloads* conj (json/parse-string input true))
{:exit 0 :out "" :err nil})]
(lifecycle/finish-chat-prompt! :idle {:db* (h/db*)
:config (h/config)
:chat-id "sub-1"
:agent "explorer"
:messenger (h/messenger)
:metrics (h/metrics)}))
(is (= ["postRequest" "subagentPostRequest"] @fired-types*)))))
(is (= ["postRequest" "subagentPostRequest"] (mapv :hook_type @payloads*)))
(is (= ["parent-1" "parent-1"] (mapv :parent_chat_id @payloads*))))))

(deftest subagent-postrequest-skipped-when-postrequest-stops-test
(testing "a successful postRequest continue:false stops the turn and skips subagentPostRequest"
Expand Down