* fix(hooks): allow scoped hook wakes for unscheduled targets; keep announce wake aligned with queued session
- heartbeat-wake-policy: add isTargetedImmediateHookWake so a scoped hook
wake (source=hook, reason=hook:*) counts as an immediate targeted wake;
a known agent without a recurring heartbeat schedule can now be woken
once so its queued hook event gets consumed instead of sitting unread.
- scheduler + execution: accept the hook wake in the allowsUnscheduledTarget
gate alongside notifications-event wakes; unconfigured agents stay rejected.
- hooks announce path: only pass agentId when the hook explicitly named an
agent; an unnamed hook resolves its event session from fresh config at
announce time, so pairing the dispatch-time default agent with that
session could wake a stale agent after a default-agent reload.
- regression tests: targeted hook wake for an unscheduled agent runs once;
unconfigured hook targets rejected; announce wake omits default agentId
for unnamed hooks.
* test(infra): split hook-wake heartbeat tests out of scheduler test file
Keep heartbeat-runner.scheduler.test.ts inside the oxlint max-lines budget
(1045 -> 995 effective lines) by moving the two hook-source wake targeting
tests into heartbeat-runner.hook-wake.test.ts. No production code changes.
* fix(gateway): target global-scope hook wakes at the fresh default agent
Global session scope resolves hook event keys to the unscoped "global"
sentinel, which carries no agent identity. The scheduler refuses to
resolve a target from it, so announce and failure wakes for unnamed
hooks left the queued event unread.
Carry the fresh default agent alongside the global key on both paths
(announce and failure), mirroring the enqueue-wake path. Non-global
unnamed-hook wakes are unchanged: their session key embeds the agent,
and pairing a frozen default agent could wake a stale agent after a
default-agent reload.
* fix(gateway): target recovered global failure wakes at the explicit or fresh default agent
The failure-wake agent selection gated both branches on hookEventSessionKey.
When initial event-key resolution fails (early config failure), that key is
absent even though recovery still yields the unscoped "global" sentinel; the
wake then carried no agentId and the queued failure event sat unread.
Use the recovered session key for the sentinel check and attach the explicit
agent, or the fresh default agent, so the global failure event is consumed.
Non-global unnamed-hook wakes are unchanged: their session key embeds the
agent, and pairing a frozen default agent could wake a stale agent after a
default-agent reload.
* fix(gateway): preserve agent identity for global hook wakes
Signed-off-by: sallyom <somalley@redhat.com>
* test(infra): allow seeding a heartbeat session without delivery fields
The new hook-wake test seeds an empty session for the hooks agent, which
has no real delivery context. Relax seedSessionStore's seed type to
Partial so empty seeds typecheck.
* fix(gateway): keep global hook wakes on the dispatch-time agent
The announce and failure wake paths resolved the default agent from
fresh config at queue-execution time. If a default-agent reload happened
between dispatch and execution, a global-scope wake retargeted at the new
default while the isolated run used the accepted agent, so the event
produced under one agent was consumed by another. Freeze the accepted
agent at dispatch (acceptedHookAgentId) and prefer it in both wake paths;
fresh resolution remains the fallback for pre-acceptance failures. Adds
announce + failure reload regressions (default main -> work mid-queue).
* test(hooks): annotate reload fixtures as OpenClawConfig
The default-agent reload fixtures widened session.scope to string and
fixed agents.entries to the first assignment's shape, breaking
check-test-types. Annotate both fixtures so each reload conforms to the
config contract.
* fix(hooks): partition global-scope hook events by owning agent
Global-scope hook announce and failure events all land in the shared
literal `global` system-event queue while each wake targets a single
agent, so concurrent hook completions can cross-consume each other's
events: the first targeted wake drains every agent's queued result or
failure into one prompt.
Give transient system events an ownerAgentId set by the hook dispatch
paths (announce, failure, and wake-only enqueues) whenever the event
lands on the unscoped sentinel queue, and filter the heartbeat preflight
peek to the running agent's owned events. The store's literal global
session row stays shared; unowned events remain visible to every agent,
so non-hook global events are unchanged.
Add a two-agent concurrent regression proving the first targeted wake
leaves the other agent's event queued for its owner.
* fix(hooks): isolate global events by agent
Signed-off-by: sallyom <somalley@redhat.com>
* fix(infra): unexport internal targeted-wake helpers
isTargetedImmediateSystemEventWake and isTargetedImmediateHookWake are
only consumed by isTargetedImmediateUnscheduledWake within the same
module; keep them module-private so knip deadcode stops flagging the
unused exports.
---------
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
* fix(exec): acknowledge notify-on-exit after process poll
* test(exec): mock completion event enqueue
* fix: retire stale exec heartbeat wakes
Prevent acknowledged process completions from turning their queued wake into an unrelated heartbeat. Preserve coalesced task work and keep stale wakes out of scheduler cadence and commitment follow-up.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: isolate stale exec heartbeat coverage
Keep existing heartbeat suites below the max-lines ratchet while retaining coverage for stale wake coalescing, scheduler bookkeeping, and commitment fan-out.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(heartbeat): preserve scheduled cadence for stale exec wakes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(heartbeat): keep stale wake coverage within lint limits
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(heartbeat): retire stale exec wakes before busy gates
Preserve scheduled cadence and cron work when coalesced exec wakes are acknowledged, while retiring stale wakes before retryable busy checks.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(heartbeat): accept inferred wake sources
Allow stale-wake preflight to handle the optional source produced by reason inference without weakening the exec-event check.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: delete dead infra and config exports
* refactor: preserve live infra and config contracts
* refactor(config): remove obsolete file-store lifecycle APIs
* refactor(infra): finish current-main dead export cleanup
Clean up completed exec tool-call abort listeners so normal foreground completion and background-yield no longer retain the exec run/session context through AbortSignal listener state.
The listener cleanup now lives beside the exec listener registration and runs when the foreground process settles, rejects, or the tool returns a background running result. Existing abort/timeout/background behavior remains owned by the process supervisor and process registry.
Verification:
- gh pr checks 83022
- gh api repos/openclaw/openclaw/commits/fe86528ecb2043b6febef5c2eec53f9124be5543/check-runs
- git merge-tree --write-tree origin/main refs/remotes/pr/83022
- git diff --check origin/main...refs/remotes/pr/83022
- node AbortSignal add/remove listener probe
Thanks @c19354837.
Co-authored-by: Ninty <c19354837@hotmail.com>
Keep queued system-event owner downgrades as structured runtime metadata while rendering the model-visible prompt as plain `System:` lines.
This preserves least-privilege wakeups for webhook/node/exec/cron/reaction/hook producers, keeps legacy `trusted: false` compatibility for installed plugins and older hosts, and updates representative gateway, agent, cron, plugin, and OpenGrep coverage.
* clawdbot-a2c: pin exec completion delivery context
Regeneration-Prompt: |
Fix a Telegram forum topic misroute where delayed exec completion or similar async completion text could be delivered into the wrong topic after the session's stored route drifted. Keep the patch surgical. Preserve immutable origin deliveryContext when background exec completion events are queued, thread that context from the exec tool's ambient channel/session defaults into the process session, and ensure the queued system event carries it instead of relying on later heartbeat fallback to mutable session lastTo/lastThreadId data. Add one focused unit assertion that notifyOnExit events keep the original Telegram topic delivery context and one heartbeat regression that proves work started in topic 47 still delivers back to topic 47 even if the session store later points at topic 2175.
* fix: note Telegram exec topic routing
Regeneration-Prompt: |
Prepare PR #64580 after review-pr with no blocking findings. The only required prep change was the workflow-mandated changelog entry under CHANGELOG.md -> Unreleased -> Fixes. Preserve the review conclusion that the code change is already acceptable, do not widen scope beyond the changelog, and include the PR number plus thanks attribution in the changelog line for the Telegram exec forum-topic completion routing fix.
* fix(exec ): stop emitting tool updates after session is backgrounded
When an exec session is backgrounded (background: true), the owning
agent run resolves its tool-call promise and may finish. The stdout
handler's emitUpdate() closure, however, kept invoking opts.onUpdate(),
delivering tool_execution_update events to a listener whose active run
had already ended. This surfaced as an unhandled rejection and crashed
the gateway process.
Guard emitUpdate() with a session.backgrounded || session.exited check
so that post-background output is still captured via appendOutput() but
no longer forwarded to the (now-stale) agent-loop callback.
Fixes#61592
* style: trim exec backgrounding comments
* fix: stop emitting post-background exec updates (#61627) (thanks @openperf)
* fix: place exec changelog entry at end of fixes (#61627) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(agents): prefer completion wake over polling
* fix(changelog): note completion wake guidance
* fix(agents): qualify quiet exec completion wake
* fix(agents): qualify disabled exec completion wake
* fix(agents): split process polling from control actions
When an exec command fails (e.g. timeout), the tool previously rejected
with an Error, which the tool adapter caught and wrapped in a JSON object
({ status, tool, error }). The model then received this raw JSON as the
tool result and could parrot it verbatim to the user.
Now exec failures resolve with a proper tool result containing the error
as human-readable text in content[], matching the success path structure.
The model sees plain text it can naturally incorporate into its reply.
Also fixes a pre-existing format issue in update-cli.test.ts.
Fixes#52484
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>