* fix(macos): host the dashboard sidebar toggle beside back/forward in the titlebar
The Control UI's floating sidebar-expand button rendered as a bordered web
control crowding the traffic lights in the dashboard window. The toggle now
lives as a native borderless button in the leading titlebar accessory ahead
of back/forward (Safari ordering) and bridges to the web UI via the
openclaw:native-toggle-sidebar event; the injected chrome script advertises
the capability with an openclaw-native-nav class so the web control retires
itself visually while staying keyboard/screen-reader reachable.
* docs: note the macOS app's native titlebar sidebar toggle in the Control UI guide
* origin/main:
fix(cron): abort superseded reconciliation hooks (#104368)
ci(release): expose Telegram runtime preflight stage (#104387)
fix(status): avoid false shell-wrapper audit warnings (#81778)
fix: retry live Gateway readiness proof (#104374)
fix(exec-approval): stop misattributing Allow Always unavailability to policy (#97740)
fix: preserve Mac bundle during live builds (#104376)
chore(docs): translate with GPT-5.6 xhigh
test: wire Control UI suite into CI, fix its broken tests, drop dead harness surface (#104361)
fix(ui): hide group submenu separator when New group is the only entry (#104370)
fix(discord): reset progress drafts across queued turns (#102341)
docs(cli): clarify that exec-policy show and approvals get exclude per-session /exec overrides (#94999)
fix(discord): single-source thread-binding default placement and guard artifact parity (#104342)
fix(agents): add tool-activity heartbeat to keep subagent alive during tool calls (#95536)
fix(maint): reuse recent same-PR hosted gates (#104355)
* fix(exec-approval): stop misattributing Allow Always unavailability to policy
Allow Always is dropped both when policy ask=always AND when a command is
non-persistable (e.g. shell redirect `2>&1` -> one-shot), but the prompt always
claimed 'effective approval policy requires approval every time'. That's
misleading for the non-persistable case (#97069). Reword to reason-neutral
'Allow Always is unavailable for this command.' across all approval surfaces,
update en + 20 locale bundles, refresh i18n meta, and the matching tests.
Closes#97069
* fix(exec-approval): stop misattributing Allow Always unavailability to policy
* test(ui): await exec approval render updates
* chore(ui): sync approval i18n metadata
---------
Co-authored-by: saju01 <saju01@users.noreply.github.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
* test: wire Control UI suite into CI, fix its broken tests, drop dead harness surface
- fix 7 Control UI tests broken on main: module-mock factories are unreliable
under isolate:false shared workers; use RealtimeTalkSession prototype spies
and real jsdom storage instead (chat-realtime, chat-pull-requests)
- add gated checks-ui CI job (runUiTests changed-scope output): chromium
provisioning, lint:ui:no-raw-window-open, pnpm --dir ui test; ~30s on a
4vcpu runner, runs only when ui-affecting paths change
- add weekly node22-compat workflow exercising the supported lower Node bound
with the same command set as the dispatch-only ci.yml job
- delete the unreachable channels entry in EXTENSION_TEST_CONFIG_ROUTES and
the never-populated extensionRoutedChannelTestFiles override machinery
(born empty in 2ccb5cff22); resolved globs are provably identical
- guard run-vitest.mjs hardcoded path maps with existence tests so renames
cannot silently drop extended stall watchdogs
- remove unenforced coverage thresholds; test:coverage is informational, docs
updated (reference/test, help/testing, plugins/sdk-testing)
Closes#104321
* test: pin OPENCLAW_CI_RUN_UI_TESTS in the manifest env expectation
* test(ui): capture the realtime start spy instead of referencing the unbound prototype method
* test(ui): raise ui vitest timeouts for real-browser layout tests on small CI runners
* fix(discord): guard progress draft collapse to prevent re-collapse on cleaned stream
Commit 86ea382 added a progressDraftStartedBeforeFinal latch that snapshots
hasStarted at markFinalReplyStarted time. This correctly preserves collapse
eligibility when an abort cancels the compositor gate before
shouldCollapseProgressDraft runs.
However the latch was never cleared. After markPreviewFinalized turned the
draft into a summary, hasProgressDraftStarted remained true permanently. In
multi-tool turns with multiple final-payload deliveries, this caused
shouldCollapseProgressDraft to re-trigger collapse on an already-cleaned or
sealed draft stream, corrupting downstream delivery state and causing tool
results to render as image content blocks instead of text.
Fix: add a progressDraftCollapsed guard set by markPreviewFinalized that
prevents hasProgressDraftStarted from returning true after the draft has been
collapsed into a summary. Reset the guard on handleAssistantMessageBoundary
so followup/queued turns start fresh.
Per Discord channel session, all tool results rendered as image content
blocks. Model received (see attached image) placeholders instead of text.
Fixes#100782
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(discord): use valid table/chunk mode literals in collapse-guard tests
* test(discord): add abort-race gate-cancellation latch survival test
* fix(discord): consume progress draft collapse once
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* fix(discord): retain progress receipt on retry
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* fix(discord): rearm queued progress drafts
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* test(discord): assert progress lifecycle outcomes
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* fix(discord): reset queued progress turn state
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* test(discord): model recreated progress drafts
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* fix(discord): serialize progress draft rotation
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* fix(discord): isolate queued draft generations
Co-authored-by: Peter Lee <xialonglee@users.noreply.github.com>
* test(discord): align synchronous draft rotation
Co-authored-by: Peter Lee <xialonglee@users.noreply.github.com>
* fix(discord): distinguish draft rotation ownership
Co-authored-by: Peter Lee <xialonglee@users.noreply.github.com>
* chore: keep release notes in pull request
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* [AI] fix(agents): add run-scoped tool-activity heartbeat to prevent premature subagent idle timeout
Subagent LLM idle watchdog tripped during long-running tool calls because
tool completions did not reset the idle timer.
Implement a per-run tool-activity heartbeat:
1. notifyToolActivity(runId) — published from embedded-runner tool
execution wrappers (attempt.ts) whenever a tool completes
2. onToolActivity(runId, listener) — consumed by streamWithIdleTimeout
to reset the idle watchdog on tool completion
3. getLastToolActivityMs(runId) — shared timestamp Map so pre-stream
activity is visible to armTimer before the first stream iteration
4. clearToolActivityRun(runId) — wired into the run lifecycle finally
block to clean up listener sets and timestamps when the run exits
Scoped per-run via runId-keyed Maps to prevent concurrent runs from
resetting each other's idle watchdogs.
Related to #94124
* [AI] fix(agents): copy plugin/channel/before-tool-call metadata onto heartbeat tool wrappers
* [AI] test(agents): add metadata preservation regression tests for heartbeat tool wrapper
* [AI] fix(agents): preserve terminal presentation metadata on heartbeat tool wrapper
* [AI] chore: update test comment for four metadata copy calls
* fix: add periodic heartbeat during tool execution
Fire notifyToolActivity at tool start and every 60s via setInterval
so long-running tools survive the 120s LLM idle watchdog.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: remove useless catch clause (no-useless-catch)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: preserve Code Mode control-tool identity through heartbeat wrapper
Adds copyCodeModeControlToolIdentity() to replicate the WeakSet
membership that markCodeModeControlTool() sets. Without this,
the heartbeat wrapper drops Code Mode identity for exec/wait
tools, weakening before_tool_call policy and approval handling.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: wrap deferred directory tools with heartbeat
resolveDeferredTool hydrates hidden catalog tools outside the
effectiveTools.map() wrapper, so they bypassed the periodic
notifyToolActivity heartbeat. Wrap the hydrated execute function
with the same start/interval/complete notification pattern.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat(tooling): add tsgo typecheck lane for scripts/**
* fix(scripts): burn down scripts type debt surfaced by the new lane
Typing-only except bugs the lane surfaced: gh-read timeout race,
Discord Headers spread dropping entries, undefined allowedHeadBranches
match, plugin-boundary matchAll crash. Deletes retired config keys from
fixtures/benches (prompt snapshots regenerated, config dump only) and
the orphaned non-runnable sync-moonshot-docs script. Adds full-surface
.d.mts declarations for existing .mjs boundaries.
* fix(watch): defer restart when dist/ is mid-rebuild (#99603)
Defer gateway:watch child restart when run-node's build/runtime-postbuild
readiness contract reports a hard dist/ failure. Only defers on conditions
where the child physically cannot start, preserving normal restart behavior
for soft staleness that run-node can rebuild on start.
Hard failures (defer restart):
- missing_dist_entry (mid-rebuild)
- missing_bundled_plugin_dist_entry
- missing_private_qa_dist
- missing_runtime_postbuild_output
Soft staleness (allow restart, run-node rebuilds):
- missing_build_stamp (w/ direct entry.js check for masked case)
- git_head_changed, dirty_watched_tree
- config_newer, build_stamp_missing_head, source_mtime_newer
isBuildReadyForRestart delegates to resolveBuildRequirement and
resolveRuntimePostBuildRequirement from run-node.mjs, using the
watcher process environment for consistency with the child runner.
200ms polling with 5-min timeout. Child-exit bounded recovery.
Clears stale restartRequested on timeout/poll errors.
Ref: #99603
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(watch): deduplicate deferral loops, add single-flight guard, remove lint suppression
- Extract deferUntilBuildReady(onReady, onTimeout) shared helper,
replacing the near-identical pollBuildReady and pollAfterChildExit.
- Add single-flight guard (deferredRestartActive) so concurrent file
change events during deferral coalesce into one poll loop instead of
spawning a git-subprocess storm.
- Replace inline .catch arrow callback with method-style call, removing
the oxlint-disable-next-line suppression and its allowlist entry.
- Add tests: coalescing multiple changes, requestRestart recovery.
Ref: #99603
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: keep gateway watch alive during concurrent builds
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(mac): prerelease-exact launch resolution and policy-aware update ownership
* fix(ui): keep update affordance when nav is hidden and fall back on native decline
* chore(i18n): sync native inventory
* feat(control-ui): add chat selection popup with More details and Ask in side chat
* fix(control-ui): keep BTW pending card visible and clear it on resultless terminal runs
* fix(control-ui): route failed BTW runs to an error side-result card and ignore stale side results
* fix(control-ui): correlate BTW pending cards by pre-generated run id and suppress superseded runs
* fix(control-ui): retire abandoned BTW runs so late side events never reach the transcript
* chore(docs): regenerate docs map for btw selection-popup section; fix selection-popup test lint