* fix(browser): bound batch action nesting depth in act request normalization
normalizeActRequest recursed over batch nesting with no depth bound, so a
~1MB POST /act body with tens of thousands of nested batch levels parsed
fine and then crashed normalization with RangeError: Maximum call stack
size exceeded before the ACT_MAX_BATCH_ACTIONS count check could run,
surfacing an internal stack overflow as the 400 validation message.
Thread the existing ACT_MAX_BATCH_DEPTH limit through normalizeBatchAction/
normalizeActRequest and reject deeper nesting up front with a clear
'batch nesting exceeds maximum depth of 5' error, matching the bound the
Playwright executor already enforces at dispatch time.
* fix(browser): match batch depth executor boundary
Accept the six wrapper levels supported by the Playwright executor and reject the seventh during request normalization.
Co-authored-by: 万拥 0668000723 <wan.yong@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The live working indicator read '592 output tokens' next to the whimsical
progress phrase, duplicating liveness signals and diverging from the
turn-recap wording ('592 tokens'). Now the whimsical phrase shows only
while no usage data exists; once tokens stream, the count replaces it and
reuses the shared turnRecap token labels. Removes the redundant
chat.outputTokens i18n key.
Replace the bordered activity-group card (border + background + hairline
row separators + per-row radius flattening) with a flat indented list and
a single 2px left rail. Every tool row now shares the same 6px hover pill
inside and outside groups, removing the square/round corner mismatch.
Also scope the generic .chat-bubble:hover accent-border rule away from
tool-shell bubbles: it tinted the group's hairline separators red on
hover, which read as an error state.
* fix(agents): keep stalled turns visible after Responses streams settle
A bare-continue turn in live QA (goal-followthrough-live, gpt-5.4 via
openai-responses) completed its SSE requests and then produced no terminal
result, delivery, timeout, or error for 6+ minutes until SIGTERM. Root cause:
turn liveness is enforced only while awaiting provider stream events (llm-idle
watchdog); queued subscription handlers are fire-and-forget during the turn and
finalize joined them unbounded and un-abortable, backed only by the 48h default
run budget. One hung delivery handler silently dead-ended the whole turn.
- Bound the pending-events join in attempt-stream-finalize with a 120s liveness
deadline plus the run-abort signal; on expiry, warn with the runId and proceed
to settlement so the run always yields a visible terminal outcome.
- Responses transports now report every SSE event via notifyLlmRequestActivity
(parity with completions/anthropic), so bookkeeping-only events keep the idle
watchdog quiet instead of counting as network silence.
* fix(agents): bound the settlement block-reply flush with the shared liveness join
ClawSweeper P1 on #120426: after the finalize-phase join times out, settlement
still awaited onBlockReplyFlush on the same wedged delivery chain (unbounded on
the supported blockReplyTimeoutMs: 0 path). Generalize the bounded join into
joinWithRunLivenessDeadline in run/abortable.ts (owner of abort/liveness
racing) and use it for both the pending-events join and the settle flush;
timeout and abort resolve with a recorded warning so the turn always reaches a
visible terminal outcome. New coverage: helper tests (hang, abort, rejection)
and a real-settle-path regression holding the flush past the deadline.
* feat(claws): export reviewed native bootstrap
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(claws): fail export when the package bootstrap drifted
Export re-emitted BOOTSTRAP.md only while the seeded copy was still pending, so
an agent whose bootstrap had been edited, flagged unsafe, or become unreadable
exported a package with no bootstrap at all. That is the same class of silent
loss the managed workspace files already guard against, so treat it the same
way: drifted bootstrap state now fails with `bootstrap_drifted` unless the
author supplies a reviewed `--bootstrap` replacement. A consumed bootstrap
stays a completed lifecycle state and still exports without BOOTSTRAP.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(claws): bind pending bootstrap export bytes
* fix(claws): preserve current export ownership limits
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(matrix): ignore out-of-range hex escapes in env account tokens
decodeMatrixEnvAccountToken guarded String.fromCodePoint with
Number.isFinite, which does not bound the Unicode range: a MATRIX_*
env var whose _X<hex>_ escape exceeds 0x10FFFF (e.g.
MATRIX_A_X110000_B_HOMESERVER) threw RangeError out of
listMatrixEnvAccountIds, crashing discovery of every env-backed
Matrix account during startup and doctor checks. Escapes above the
Unicode max are now rejected like any other malformed token.
* chore(matrix): tighten decoder invariant comment
Co-authored-by: 万拥 0668000723 <wan.yong@xydigit.com>
* chore(matrix): clarify decoder invariant
Co-authored-by: 万拥 0668000723 <wan.yong@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ai): preserve Responses server compaction state
Persist opaque Responses compaction items as fenced provider replay state so long stateless sessions can resume authoritative compressed history without exposing it in display or diagnostics. Carry state through worker transcripts and prune replay prefixes without splitting tool pairs.
Release note: Preserve long OpenAI Responses sessions across server-side compaction and worker restarts.
Related: #95788
* test(ai): align long-context fixtures with CI contracts
Make tool-result fixtures type-complete, use the canonical model selector helper, remove unused test-helper exports, and route the paid long-context live probe through the dedicated Gateway profile shard.
* test(ai): type mocked Responses terminal events
Give the mock SSE event collection an explicit open event shape so terminal response events coexist with output-item events under the root test typecheck.
* fix(ai): suppress rejected compaction replay
Persist a route-fenced suppression tombstone when encrypted-content recovery rejects a compaction item, so later turns do not retry the same opaque state. Preserve the tombstone through transcript redaction and cover successful fallback followed by the next turn.
* fix(ai): keep compaction suppression transport-private
Keep the suppression contract local to its sole Responses transport owner and make the regression fixture satisfy root type and lint checks without widening the Plugin SDK surface.
* refactor(ai): remove compaction suppression re-export
* fix(ai): scope compaction suppression to replay route
Keep foreign-route rejection tombstones from hiding the newest compatible Responses compaction while preserving same-route suppression.
* fix(ai): harden Responses replay recovery
Stage encrypted replay recovery so compaction is only suppressed after an attributable rejection. Preserve terminal ordering and keep provider replay within worker frame budgets without truncating opaque state.
* refactor(ai): centralize Responses output indexes
Keep normalized output identity tracking in the stream-slot owner, move response failure state to its diagnostic owner, and remove the obsolete replay clone export so exact-head static gates remain shrink-only.
* fix(ai): retain idless terminal tool identity
Use the canonical empty identity only when a provider supplies neither call nor item id, preventing terminal recovery from duplicating a done-only tool call while preserving stronger identities when available.
* fix(sessions): hide provider replay from public events
* fix(ai): stage encrypted replay recovery
* fix(ai): keep replay attempt kind internal
* fix(ai): route Azure through replay recovery
Use the shared encrypted-content retry owner for Azure Responses so compaction suppression and prompt-observer variants stay coherent across transports.
* fix(ai): harden replay persistence boundaries
Fence Azure replay by the resolved request endpoint, drop invalid replay during transcript sanitization, and surface worker-launch replay omissions through the existing redacted diagnostic path.
Keep per-environment provider work serialized through actual settlement after caller-visible timeouts, so replay, destroy, and shutdown cannot overlap a still-running setup.\n\nCloses #120651
* docs: runners plan — one placement model for sessions, devices, and cloud
Proposal for unifying where sessions run: gateway/devices/cloud as runners
behind the existing worker admission stack; session-continuation ergonomics;
one-paste device pairing; derived projects read model; naming rulings
(session, devices); deletion ledger. Encodes the adversarial-review
kill-list as non-goals with code evidence.
* docs: unwrap PR refs so markdownlint MD018 passes
* refactor(cli): inline state migration policy
Remove the single-use exported helper so migration policy now lives at ensureConfigReady, its lifecycle owner. Move duplicate argv and preaction coverage into the lifecycle-owner test table while preserving behavior. This follows #120451.
* refactor(cli): preserve gateway migration rationale
Keep the ownership invariant beside the inlined gateway-call exception.
* feat(update): scheduled update campaigns with countdown, dev-channel auto-update, and Updates settings page
* fix(ui): satisfy update campaign deadcode checks
* feat(update): extend scheduled update campaigns
* test(ui): include Updates page in settings sidebar search expectations
* fix(update): end campaigns when apply fails
* fix(update): pin dev campaign applies to the announced commit
* test(ui): align update e2e fixtures
* fix(update): pin adopted dev updates to the announced commit
* docs(update): document automatic dev update campaigns
* fix(ui): gate automatic dev updates on git installs
* fix(gateway): scope detailed update metadata to read-capable clients
* fix(gateway): break post-attach madge cycle with a leaf client-lookup type
* chore(protocol): regenerate clients after rebase
* test(gateway): update method tail after rebase
* fix(update): preserve campaign target and hold state
* test(update): align adoption mocks and split handoff suite
* test(update): route split handoff suites on Windows
* fix(agent): apply steering before unstarted tools
Restore steering checkpoints before sequential tool launches and before parallel batch launch. Preserve paired synthetic tool results, async callback compatibility, and Code Mode outcome handling.
* fix(agent): delay tool loop admission commits
Commit loop-detection history only for calls crossing the final launch checkpoint. Release steering-skipped markers, add repeated-steer coverage, and align remaining steering contract text.
* fix(agent): keep tool admission lifecycle internal
Attach delayed admission callbacks through the private internal-hooks seam so steering history remains correct without widening the public Agent Core or Plugin SDK contract.
* fix(agent): preserve steering API contracts
Keep public steering callbacks Promise-based and protocol error kinds unchanged. Use private synchronous draining and structured skip details to retain launch-boundary behavior without API or generated protocol drift.
* test(gateway): use canonical steering fixture config
Use keyed agent entries in the real gateway steering harness so current main does not migrate the fixture during startup.
* fix(agent): remove unused lifecycle re-export
* fix(agent): gate tool launch after wrapper preflight
Split OpenClaw tool execution into private prepare and launch phases so steering is checked after policy, approval, validation, and reconciliation but before the original side effect. Preserve final arguments, voice grants, loop admission, context wrappers, and direct tool execution.
* fix(agent): preserve steering callback receiver
Invoke public steering callbacks with their AgentLoopConfig receiver and cover method-style implementations that read config-owned queue state.
The visible-dock union existed twice: BoardVisibleChatDock (hand-written
literal union in lib/board/settings.ts, persistence layer) and
VisibleBoardDock (derived from the protocol BoardTab shape in
chat-pane-shared.ts, render layer). Follow-up from #120487.
Keep one canonical exported type in lib/board/settings.ts, now derived
from BoardTab so persisted settings can never drift from the wire
contract, and migrate all render-layer consumers to it. Net -5 LOC,
type-only.
Targeted config writes retain validation while Doctor and the gateway own state migration.
Fixes#120413
Co-authored-by: Peter Steinberger <steipete@gmail.com>