Commit Graph

490 Commits

Author SHA1 Message Date
Josh Avant ffc94d0ff0 fix: stop forked commands after Gateway timeouts (#124081)
* fix: stop service child trees before timeout completion

* fix: preserve service child root results during cleanup

* test: isolate relay selection from linux spawn wrapping

* refactor: keep service anchor payload internal

* test: validate lifecycle probe pid output

* fix: close service child adversarial races

* fix: preserve service startup failures

* fix(process): flush service output before root result

* fix(process): bound cleanup on held output

* chore(process): satisfy promise executor lint

* fix(process): finish cleanup after lineage loss

* fix(process): retain relay output before subscription

* fix(ci): register service child runtime entries

* fix(ci): expose relay safety invariants

* fix(process): preserve service cancellation grace

* test(process): prove service grace on macOS
2026-08-16 16:02:34 -07:00
Peter Steinberger 800a0bb52a perf(webui): render cached model catalog while the chat pane refreshes (#124868)
* perf(webui): render cached model catalog while the chat pane refreshes

* test(webui): fix seed-test types and shrink assertion baseline
2026-08-16 15:52:26 -07:00
Peter Steinberger 080887df92 refactor(types): discriminated unions for internal contracts — pilot lane A (#124845) 2026-08-16 14:49:28 -07:00
Peter Steinberger 06101b1037 refactor(validation): consolidate boundary guards into schemas (#124820)
* refactor(validation): consolidate boundary guards into schemas — pilot lane B

* fix(meetings): reject non-object browser status
2026-08-16 14:41:14 -07:00
Peter Steinberger 9b17b93414 refactor(ui): derive cron types from gateway protocol (#124827)
* refactor(ui): derive cron types from gateway protocol

* fix(protocol): preserve cron session target type
2026-08-16 14:34:26 -07:00
Peter Steinberger ee1ec45eb3 improve(ui): one primary composer button, Enter-steer, Esc-stop, quiet mic, no transcript focus ring (#124799)
* fix(ui): drop the transcript keyboard-focus ring

The .chat-thread:focus-visible 2px inset outline (added in #120693) boxed
the entire main content whenever the transcript took keyboard focus
(Shift+Tab, closing transcript search) and read as a broken border that
stopped above the composer. The transcript stays keyboard-focusable for
scroll keys; it just paints no focus box. Accepted tradeoff: no visible
focus indicator on the scroll container.

* feat(ui): single primary composer button with Enter-steer and Esc-stop

The composer now renders exactly one primary action, always rightmost:
- draft or attachments present: one send-shaped button whose label follows
  the session follow-up mode (Send / Queue / Steer); the stop button no
  longer renders beside it
- empty while a run is active: Stop (tooltip "Stop (Esc)"); Escape in the
  composer aborts the run, deferring to reply-clear and menu precedence
- empty while idle: disabled send labeled "Write a message to send."

Enter on an empty composer while a run is active steers the oldest
steerable queued message — Enter queues, Enter again steers — using the
same eligibility predicate as the queue chip's Steer action (shared
steerableQueuedMessage helper). The mic/dictation control is now always a
compact secondary control left of the primary instead of morphing into
the primary when the composer is empty. Dead ChatRunControlsProps fields
(hasMessages, onExport, onNewSession, showPrimary, showSecondary) and the
composer exportMarkdown wrapper are removed; the assertion-safety
baseline entry for chat-composer.ts shrinks 8 -> 7.

* refactor(ui): quiet the idle composer mic

The idle mic was an accent-tinted two-segment pill (mic + permanently
visible device-picker chevron) that outweighed the primary action. It now
matches the attach button's ghost weight: transparent idle, subtle hover,
and the accent treatment is reserved for the recording and talk-active
states so the idle -> recording escalation reads stronger. The device
picker chevron collapses at idle and reveals on hover/focus-within (kept
always visible on coarse pointers, where dictation matters most; reveal
transition disabled under prefers-reduced-motion). The idle tooltip now
teaches the gesture split — "Tap to talk · Hold to dictate" — while the
aria-label stays "Start voice input".

* refactor(ui): split composer keydown and oversized composer test files

chat-composer.ts crossed the 700-line oxlint budget after the keyboard
additions; the keydown handling (menu routing, history keys, Escape-stop,
Enter steer/send) moves to chat-composer-keydown.ts behind a typed deps
factory. The composer unit tests split their primary-action/keyboard
coverage into chat-composer-actions.test.ts over a shared
chat-composer.test-support.ts, and the catalog/model-discovery e2e cases
move to chat-composer-catalog.e2e.test.ts. Pure moves, no behavior
change; no max-lines suppressions or baseline entries added.

* chore(ui): keep composer test-support ComposerProps internal

The split exported a type nothing imports; the dead-export scan is a
merge gate.

* fix(ui): narrow composer keydown target and adapt keyboard tab-order proof

The keydown split moved two bare textarea assertions into a zero-baseline
file; a single instanceof narrowing at the handler entry replaces both
(the handler only binds to the composer textarea). chat-composer.ts's own
assertion baseline ratchets 7 -> 5. The pointer-activation e2e now walks
the real post-redesign tab order (mic, focus-revealed picker trigger,
then primary send) instead of assuming one Tab.

* fix(ui): gate Enter-steer on the queue chip's visible availability

ClawSweeper P1: the Enter path only required an abortable run, so
offline Enter swallowed the keypress and invoked a steer lifecycle that
returns at !connected with no visible outcome, while the queue chip's
Steer button was correctly hidden behind connected && canCompose. The
keydown path now mirrors the chip's exact boundary (connected, canSend,
abortable, visible queue surface including sessionKey-less pending
rows).
2026-08-16 14:00:47 -07:00
Peter Steinberger 707d3f4adf refactor: split max-lines pilot files and retire suppressions (#124813) 2026-08-16 13:45:27 -07:00
Peter Steinberger 32f056f011 refactor(agents): persist directive facts at the assistant write boundary (#124793)
* refactor(agents): persist directive facts at the assistant write boundary

Assistant final text is now stripped of inline delivery directives once, at
the SessionManager append boundary, with the parsed facts persisted as a
typed openclawDelivery field on the assistant message (openclawDeliveryMirror
precedent, inside event_json — no schema change). Live session state is
replaced with the persisted canonical bytes so same-run continuations and
next-turn store rebuilds never diverge (prompt-cache invariant).

Delivery and restart recovery consume the stored facts instead of re-parsing
persisted text; pre-upgrade in-flight messages without facts intentionally get
no text-parse fallback (commented tradeoff at the read site). Directive
parsing is now code-region aware, so markers quoted in inline code or fences
neither parse nor strip — fixes the empty-code-pill display bug.

The undocumented reaction marker DSL ([[react:]], [[react_to_current:]]) is
deleted; structured message-tool reactions remain the canonical path. Stale
assertion-safety baseline entry removed with the parser.

Production net -10, tests net +38. Focused suites: 9,068 passed; pre-fix
regressions fail for the intended reasons. Codex autoreview clean.

* fix(agents): satisfy directive persistence gates

* fix(config): drop unused directive export

* fix(agents): preserve indented directive examples
2026-08-16 13:44:11 -07:00
Peter Steinberger 541f373bc0 refactor(validation): type producers and drop internal re-validation — pilot lane C (#124808) 2026-08-16 13:38:17 -07:00
Peter Steinberger d8ebe85c24 refactor(line): replace nine-marker prompt DSL with typed rich messages (#124755)
* refactor(line): replace nine-marker prompt DSL with typed rich messages

Delete the LINE plugin's double-bracket marker language (quick_replies,
location, confirm, buttons, media_player, event, agenda, device,
appletv_remote) and its parser. Portable interactions now flow through the
existing presentation-block seam (renderPresentation, matching Discord and
Feishu); LINE-specific cards ride closed channelData.line schemas mapped to
the existing Flex renderers. Prompt section shrinks to four capability
lines and explicitly de-fangs marker text. Removes the stale
assertion-safety baseline entry for the deleted parser.

Production LOC net -69, tests net -433. Suite: 510/510 green.

* fix(line): declare rich message schema dependency

* fix(line): satisfy rich message type checks

* docs(line): mark card fragments as partial
2026-08-16 12:45:39 -07:00
Peter Steinberger eb77ce5690 refactor(infra): split device pairing lifecycle modules (#124734) 2026-08-16 11:40:55 -07:00
Peter Steinberger edf4c74601 fix(ci): prevent QA maturity scorecard timeouts (#124612)
* fix(ci): prevent QA maturity scorecard timeouts

* fix(ci): register QA evidence shard entrypoint

* fix(ci): isolate QA evidence jobs from shared caches

* fix(ci): separate QA selected revision from trusted harness

* fix(ci): protect every QA selected-code job

* fix(ci): bind QA workflow callers before checkout

* fix(ci): authenticate QA evidence caller chain

* fix(ci): remove dynamic QA checkout actions
2026-08-16 11:17:21 -07:00
Peter Steinberger aeff737da9 fix(agents): prevent invalid names from targeting the default agent (#124670)
* fix(agents): reject unrepresentable agent ids

* refactor(system-agent): split model selection setup

* chore: shrink assertion safety baseline

* docs: record strict agent id validation proof

* style: format strict agent id report

* chore: drop stray unrelated report artifact

* chore: restore REPORT.md to main state
2026-08-16 10:37:06 -07:00
Peter Steinberger 146350f417 fix(nodes): keep MCP calls within the published catalog (#124652)
* fix(nodes): align MCP calls with published catalog

* chore(tests): shrink assertion safety baseline
2026-08-16 08:20:09 -07:00
Peter Steinberger 468d621f9a fix(update): complete high-volume stable upgrades without stalls (#124651)
* fix(testing): restore published upgrade validation

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* perf(sessions): batch transcript archive workers

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* fix(cli): exit after legacy agent turns

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* fix(testing): reset upgrade fixture ports

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

* chore(lint): shrink assertion safety baseline

Amp-Thread-ID: https://ampcode.com/threads/T-01a00a6a-b64e-74a5-8b15-2d3b966a468d

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-16 08:14:56 -07:00
Peter Steinberger 6a82431494 refactor: replace runtime mock seams with injection (#124634) 2026-08-16 07:33:13 -07:00
Peter Steinberger 19bcb23ba1 fix: preserve reply order across overlapping turns (#124623)
Deliver same-target channel replies in inbound order while keeping steer admission concurrent. Record accepted steer/followup ownership so intentional deferred turns do not look silently empty.

Fixes #124618
2026-08-16 07:17:53 -07:00
Peter Steinberger 99bda3d8cd refactor: make transport stream writer types honest (#124587)
* refactor(ai): give transport streams an honest writer type

* test(ai): use canonical transport stream fixtures

* fix(ai): preserve partial-less stream deltas
2026-08-16 07:03:36 -07:00
SunnyShu 16cde04136 fix(agents): allow required-preflight native Codex compaction (#120197)
* fix(agents): allow required-preflight native Codex compaction

Required reply-preflight compaction on a Codex app-server-backed session
returns the intentional `ok: true, compacted: false` "codex app-server owns
automatic compaction" no-op because the preflight caller never passes
`allowNonManualNativeRequest`. The reply/preflight path then misclassifies
that successful skip as a failure and throws, dropping the user's turn with
"Context is too large and auto-compaction could not recover this turn." The
equivalent CLI path was fixed by #88207; this is the second, unpatched caller.

Route required-preflight through the existing private
`compactAfterContextEngine` harness capability (which already passes
`allowNonManualNativeRequest: true`) by adding a typed
`nativeCompactionRequest: "required_preflight" | "after_context_engine"`
origin on `maybeCompactAgentHarnessSession` and the Codex compact bridge.
The non-manual skip guard is bypassed for preflight, so Codex actually
compacts the thread.

A binding change between the initial read and the native request is a
stale-binding race, not a benign skip. For `required_preflight` (and the
non-manual CLI path) it now surfaces as the canonical recoverable
`stale_thread_binding` failure so the queued harness falls back to the
context engine instead of treating an uncompacted `ok: true` result as a
completed turn. A genuine post-context-engine request may still skip,
because the context engine has already compacted. Required-preflight is also
the one scoped exception to the model-locked terminal rule: missing or stale
Codex thread bindings recover via the shared context-engine fallback while
the persisted harness lock stays intact; other locked failures remain
terminal.

Rebased onto main after #120740 restructured the guarded native compaction
block; the recoverable-binding semantics are reintroduced on the new
structure and scoped by `nativeCompactionRequest` so #120740's
post-context-engine skip behavior is preserved.

Closes #119971.

* test(evidence): commit inspectable required-preflight live proof scripts for #119971

Adds the two live codex app-server proof scripts (binding-race +
locked-preflight) so the redacted terminal traces in the PR body are
inspectable on the exact head. Both drive the real codex binary and real
maybeCompactCodexAppServerSession with nativeCompactionRequest:
"required_preflight"; neither runs in CI (no codex binary).

* fix(agents): scope locked-preflight compaction fallback to Codex

Restrict the required-preflight model-lock exception to the Codex harness
so missing/stale thread bindings in other locked native harnesses (e.g.
Copilot) stay terminal instead of escaping the persisted model-lock
boundary via context-engine fallback. Add a model-locked Copilot
required-preflight regression covering both missing and stale thread
bindings.

* fix(codex): require native preflight compaction

* chore(plugin-sdk): account for native compaction exports

* test(codex): use complete cron authority fixtures

* chore(lint): shrink compaction assertion baseline

* fix(lint): honor root boundary timeout

* fix(lint): extend package boundary timeout

* fix(plugins): verify native compaction owner

---------

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-08-16 08:00:38 -06:00
Peter Steinberger 243f51d314 refactor(macos): name computer-use executors for their scope (#124586)
The computer.act v1 wire contract is gone, but the naming that survived it
still described a version split instead of the real one: screen-coordinate
execution versus window/element-scoped execution. Both are live rungs of the
same ladder.

- Extract the screen-coordinate half of the 1334-line ComputerActionService
  into ComputerScreenActionExecutor (dispatch, typing, scroll, coordinate
  mapping, button-hold watchdog, raw CoreGraphics primitives). Moved code is
  unchanged apart from threading the queue authority check as a parameter
  instead of reaching back into the queue.
- ComputerActionService keeps its name and becomes the coordinator that owns
  the execution queue, the permission probe, and the shared error vocabulary.
- Rename ComputerActionServiceV2 to ComputerWindowActionExecutor, isV2Request
  to isWindowScopedRequest, isComputerActV2Only to isWindowScopedOnly, and
  ComputerActionError.invalidV2Request to .invalidRequest. The emitted
  COMPUTER_INVALID_REQUEST: prefix is unchanged.
- cua-computer: v2-actions.ts becomes window-actions.ts, handleV2Act becomes
  handleWindowAct, and the stale v1Params local in handleDesktopAct becomes
  desktopParams.
- Note at the computer.act idempotency key that its v1 prefix versions the key
  composition, not the wire contract.

Behavior-neutral: no logic edits, no new branches, no changed error strings.
2026-08-16 06:17:31 -07:00
Peter Steinberger 32ddbb3be2 fix(ui): chat send failures were invisible when the session pane was hidden (#124473)
* fix(ui): always surface terminal chat send failures

Terminal chat send failures were recorded only on the queue item when the
owning pane was not visible (reconnect, alias drift, split-pane routing),
so the operator saw nothing at all — an action ending in silence.

Root cause: every error surface in the send/drain path was gated on
visibleSessionMatches(...), and the FIFO outbox drain treated every
chat.history reconcile rejection as a silent retryable "blocked", so a
non-retryable rejection (e.g. auth loss) wedged the head — and everything
behind it — permanently with no visible outcome.

Fix, at the owner:
- New surfaceChatDeliveryFailure() in steer-lifecycle.ts (the shared
  error-text owner): visible pane keeps the inline chat error; otherwise
  the failure routes through the existing global toast host, naming the
  session. All terminal failure sites in chat-send-delivery,
  chat-outbox-drain, chat-send-queue-state, and steer-lifecycle now use it.
- reconcileStoredChatOutboxHead: a non-retryable GatewayRequestError on the
  head now terminally fails a never-attempted head (unblocking the lane)
  or parks an attempted head as unconfirmed — both with a visible outcome —
  instead of blocking the lane forever.
- Composer disabled-reason: the reason now also renders while a draft hides
  the placeholder, and the cloud-startup-pending gate gets a reason instead
  of a silently disabled composer.

Regression tests: hidden-pane terminal failure surfaces via toast; wedged
head fails visibly and the lane drains the next message; attempted head
parks unconfirmed; disabled reason visible with draft text present.

* fix(ci): raise startup JS baseline for global failure surfacing and pin workspace-sync test clock

The chat send path now imports session-display naming for the global
failure toast, adding ~1 KiB gzip to startup JS (335452 B on CI's Linux
builder, still well under the 358400 B committed cap).

workspace-sync "never commands" asserted the exact dispatch timeoutMs
(777) but the impl derives it from a Date.now() deadline, so any elapsed
ms between admission and dispatch failed the exact-equality assertion on
a loaded runner. Pin the clock like the sibling timeout tests do.

* fix(ui): surface route-switched command failures and agent-scope global toast naming

ClawSweeper review findings on #124473:

- A queued local command failing after the operator navigated away hit
  failCommand(error) with expose=false; the dispatcher's stale-scope
  guard had already withheld the inline error, so a successful state
  write recorded the failure invisibly — the silent class this PR
  removes. Expose it globally when the scope is stale and the owning
  pane is hidden; a stale scope with the pane still visible keeps the
  failed queue chip (the new connection owns the inline surface).
- Global session rows are agent-scoped behind one shared "global" key,
  so the toast row lookup could borrow another agent's label. Match the
  row's agentId to the failed outbox's agent for global keys.

Both regression tests fail pre-fix (stash-verified).
2026-08-16 01:38:06 -07:00
Peter Steinberger 03be260e5b fix(lint): assertion SAFETY ratchet — exempt as-unknown, absorb merge drift (#124445)
* fix(lint): exempt as-unknown from SAFETY ratchet and rebaseline for merge drift

* chore(lint): refresh assertion baseline after rebase
2026-08-15 23:10:02 -07:00
Peter Steinberger 203aafc415 feat(lint): SAFETY-comment assertion ratchet + repo deslop skill (#124359)
* feat(lint): add SAFETY-comment assertion ratchet

* feat(skills): add repo deslop skill

* fix(lint): refresh initial assertion baseline

* chore(lint): align assertion baseline with main

* chore(lint): sync baseline with current assertion ledger

* test(ui): stabilize Control UI E2E waits

* chore(lint): sync narrowed assertion ledger

* chore(lint): sync fully drained assertion ledger

* fix(lint): close assertion ratchet bypasses
2026-08-15 22:03:48 -07:00
Peter Steinberger 191d2313a8 test(codex): split the session-catalog suite along module seams (#124397)
* test(codex): split the session-catalog suite along module seams

Replace the 4.8k-line grandfathered session-catalog.test.ts with seven
seam-anchored suites plus shared fixtures, and remove its max-lines
suppression and baseline entry. The broad openclaw/plugin-sdk/node-host
vi.mock becomes a narrow session-catalog-pty.runtime.ts boundary so the
non-isolated extension-codex worker no longer re-instantiates the
plugin-sdk graph once per split file (the crash PR #124178 measured).
Test bodies are verbatim; AST parity across the split checked out
96/96 identical.

AI-assisted (Codex worker under maintainer review).

* test(codex): carry caller origin in the cron-authority capability fixture

PR #118579 widened the in-process CronCreatorAuthorityCapability run
scope with a contractually required callerOrigin, and the new transcript
tool wiring reads it during turn startup. The hand-built fixture in
run-attempt.configured-mcp.test.ts predated the field, so
bindActiveOperatorTurnAuthority threw and runCodexAppServerAttempt
rejected while five tests awaited turn/start - 120s timeouts, then a
worker teardown crash. #118579's CI never ran the extension-codex lane
(cross-lane classification gap), so main's codex lane was latently red.
Bisected to 8668aeb9698; fixture now mints the local-operator origin the
helper's name promises.

AI-assisted (maintainer-diagnosed, Codex-era fixture repair).

* test(codex): keep catalog fixture internals private
2026-08-15 21:26:13 -07:00
Peter Steinberger c3488150c5 refactor(computer-tool): split by responsibility (#124358) 2026-08-15 19:38:16 -07:00
Peter Steinberger df2b83f7ae refactor(anthropic): split session catalog into concept modules (#124342)
* refactor(anthropic): split session-catalog into concept modules

Follow the #124178 sibling seam pattern while preserving behavior. Remove the max-lines suppression and baseline entry after the split.

* refactor(anthropic): own record caches in session-catalog-discovery

The record-shaped caches (catalogDiscoveryCache, claudeSessionScanCache,
their entry types, bounds, and TTLs) are consumed only by discovery; moving
them out of scan removes the scan->discovery type-import edge flagged by the
madge topology gate and leaves scan as record-agnostic fs primitives.
2026-08-15 19:18:03 -07:00
Peter Steinberger 1d53211924 refactor(version): remove obsolete define injection (#124321)
* refactor(version): remove obsolete define injection

* test: drop retired version suppression expectation
2026-08-15 18:23:14 -07:00
Peter Steinberger 78502eda6d feat(workers): run device sessions from Gateway bundles (#124037)
* feat(workers): run device sessions from Gateway bundles

Install the current Gateway bundle before a device environment becomes ready, verify it at attach and tunnel boundaries, launch only from the immutable namespaced bundle directory, and retire stale environments for idempotent reprovisioning. Remove the local execution mode and preserve the node-local build claim only as temporary inventory metadata for the final projection/cleanup slice.

* docs(runners): record Gateway bundle cutover

* test(ci): repair runner validation fixtures

# Conflicts:
#	src/scripts/test-projects.test.ts

* fix(workers): surface outdated node recovery

Keep legacy runner inventory diagnostic-only while exposing the update-and-reconnect action through node, environment, provider, placement, and Control UI surfaces.

* fix(workers): reject legacy inventory with recovery

* fix(workers): bundle worker deploy closure

* test(workers): close bundle cutover gates

* fix(workers): compose browser runtime at build

* fix(workers): satisfy bundle cutover gates

* fix(workers): route temp runtime through infra

* docs(workers): align bundle host guidance

* fix(ui): fence outdated session destinations
2026-08-15 17:46:44 -07:00
Peter Steinberger 7862a5da15 refactor(session-catalog): split codex catalog modules and unify adoption coordination (#124178)
* refactor(session-catalog): unify adoption coordination

* refactor(codex): split session catalog modules

* fix(anthropic): narrow adopted history for import

The coordinator migration hoisted history to an optional outer binding;
afterCreate needs the definite array from the create scope.

Also drop the retired session-catalog max-lines ratchet entry.
2026-08-15 06:40:40 -07:00
Peter Steinberger 25dda7968c test(config): remove orphan bundled channel runtime (#124163) 2026-08-15 05:12:06 -07:00
Peter Steinberger fe5fa802fa feat(lint): enforce no-chained-type-assertions (#124119) 2026-08-15 02:13:37 -07:00
Peter Steinberger a8f1a0d345 test(computer-use): live end-to-end gate for both macOS providers (#123991)
* fix(computer-use): preserve semantic click observations

* fix(cua-computer): isolate desktop and window sessions

* fix(cua-computer): route cursor reads through desktop scope

* test(computer-use): add isolated macOS live proof rig

* style(cua-computer): format driver sessions

* fix(cua-computer): forward lazy desktop tools

* fix(computer-use): narrow live proof artifact path

* test(computer-use): split schema contract coverage

* refactor(cua-computer): unify session start state

* build(computer-use): register live proof entrypoint

* style(computer-use): satisfy full lint contract

* fix(computer-use): harden live proof authority

* fix(cua-computer): keep window authority immutable

* fix(computer-use): close partial sessions safely
2026-08-14 23:37:53 -07:00
Jesse Merhi edb5adfbf5 Start new sessions with folder group defaults (#123276)
* feat(ui): add folder group session defaults

* fix(ci): align folder group contracts

* fix(protocol): refresh Android gateway methods

* fix(ui): reuse folder picker for group defaults

* fix(ui): harden session group defaults

* test(ui): align group defaults with current main

* test(ui): keep group catalog defaults path-free

* fix(ui): close folder group CI gaps

* fix(ui): satisfy folder group CI contracts

* fix(session-groups): enforce defaults safety

* test(gateway): keep group defaults in workspace

* fix(session-groups): enforce defaults participation

* fix(session-groups): close review authorization races

* fix(session-groups): canonicalize defaults authorization

---------

Co-authored-by: Jesse Merhi <jesse-merhi@users.noreply.github.com>
Co-authored-by: Jesse Merhi <openclaw@users.noreply.github.com>
2026-08-15 15:24:13 +10:00
Peter Steinberger 1320a0bff9 feat(lint): reject widen-then-assert type laundering (#124003)
* feat(lint): add no-widen-then-assert guard

* refactor: preserve typed boundary evidence

* refactor(types): remove redundant unknown aliases

* fix(agent): preserve replay sanitizer fallback

* fix(lint): close widen-then-assert nested-chain and const-alias bypasses
2026-08-14 22:10:19 -07:00
Peter Steinberger 2ebaebd484 fix(ui): re-attach chat transcript virtualizer after dashboard→split face switch (#123974)
* fix(ui): re-attach chat virtualizer when a foreign host re-stamps the transcript

Switching a board session from Dashboard back to Split left the docked chat
pane blank (sizer-height empty space, no rows) until an unrelated state change
re-rendered the pane or the user scrolled.

Root cause: the transcript template is stamped by openclaw-chat-sidebar-region
(it receives the chat template as a property and renders it in its own, later
update cycle), but the TanStack virtualizer only re-resolves its scroll element
inside the pane host's update. After the face-switch re-stamp no pane update
follows, so the virtualizer stayed detached: scrollElement null, scrollRect
zeroed by the hide transition, calculateRange null, zero rows painted.

Fix, at the owner (ChatSessionVirtualizerHost): attachment now follows the DOM
identity the scroll-element ref records — an identity change queues a microtask
that re-runs the virtualizer adapter's update hook. Zero-size rects from hide
transitions (display:none sidebar panels, unmount teardown) are ignored so they
can no longer wipe measured row heights or become the virtualizer viewport.

Live-verified against a remote gateway where the broken state persisted 5+
seconds pre-fix and one attachment sync healed it. Regression tests fail
pre-fix; e2e covers the deferred-latency dashboard->split flow.

* chore(ui): raise startup JS baseline for transcript virtualizer re-attach fix

Measured 333338 B gzip on PR #123974 run 31861214469 (+345 B over the base
run 31860496921 at 332993 B; prior baseline 332040 B predates recent main
drift). Absolute 350 KiB maintainer cap unchanged. Growth is the foreign-host
attachment sync in chat-transcript-controller.ts — real fix code, not
speculative surface.
2026-08-14 20:31:08 -07:00
Peter Steinberger 5760fcdae1 fix(ci): record 2026-08-15 Control UI startup JS growth in the budget baseline
Tonight's UI fix stream (stale-tab retry, session attention, talk
backpressure) consumed the 1 KiB tolerance and broke build-artifacts plus
both QA smoke ui:build paths on main (run 31861213699, 333309 B vs
332040+1024). Recorded with CI bytes per the checker contract; the 350 KiB
maintainer-approved ceiling still bounds cumulative creep.
2026-08-14 20:19:16 -07:00
Peter Steinberger 98a8e0d63f fix(macos): prevent mismatched CUA driver endpoints (#123845)
* fix(macos): make CUA driver endpoint handoff atomic

* ci: record approved CUA env budget increase

* test: satisfy changed-gate hygiene

* style(macos): use failable endpoint string conversion

* fix(macos): strip retired CUA worker environment

* ci: constrain CUA env budget approval

* test(macos): isolate inherited CUA environment
2026-08-14 16:04:43 -07:00
Vyctor H. Brzezowski 3f006ba0fc fix(ui): complete mobile pairing after code redemption (#120933)
* fix(ui): complete pairing setup lifecycle

Redeemed and expired mobile setup codes stayed on screen as usable QR
codes, so a successful pairing had no visible outcome and expired bearer
material still looked live.

The Gateway now mints an opaque, non-authorizing setupId beside each
setup credential, returns its authoritative expiresAtMs, records the
terminal outcome of the exact redemption, and broadcasts
device.pair.setup.completed. Because that broadcast is dropped for
buffered operator sockets, the completion is persisted first and can be
reconciled through the new device.pair.setupStatus method: the Control UI
asks for the recorded outcome before it may present a credential as
expired, so a pairing that succeeds is never shown as a failure.

The Control UI models one closed lifecycle (selection, loading, waiting,
success, expired, error) correlated only by the active setupId, and
Pairing help now carries the external-link affordance.

* fix(ui): preserve unknown pairing outcomes

* test(ui): target pairing terminal headings

* test(ui): align pairing completion fixture

* fix(gateway-protocol): decode setup-code results from older gateways

Older protocol-v4 gateways omit the new setupId and expiresAtMs
lifecycle fields, so requiring them in the generated native model broke
decoding an existing device.pair.setupCode response. Keep both optional
at the wire boundary, require lifecycle metadata before the Control UI
enters its waiting state so a missing outcome stays visible, and cover
the legacy payload with Swift and schema regressions.

* fix(ui): surface rejected pairing dialog loads

The lazy pairing chunk could reject while its overlay was already open,
leaving the shell rendering nothing at all. Record the rejection on the
shell and render a recoverable modal with a reason and a retry so the
open action always ends in a visible outcome.

* fix(state): preserve pairing setup schema compatibility

* test(gateway): cover pairing setup release train

* fix(gateway): commit pairing setup completion atomically

* refactor(state): distinguish setup transaction helpers

* refactor(state): remove obsolete bootstrap restore path

* fix(gateway): preserve setup handoff type safety

* fix(gateway): keep pairing completion terminal after consume

* fix(gateway): validate Watch binding during setup commit

* fix(gateway): revalidate setup credential expiry at commit

* chore: refresh Plugin SDK API contracts after rebase

* fix(pairing): prune expired setup completions

* chore: retrigger CI

* fix(protocol): deduplicate setup expiry field

* fix(protocol): refresh pairing setup clients

* fix(gateway): make pairing setup completion durable

* fix(ui): retire expired pairing credentials immediately

* fix(ui): keep pairing dialog visible while loading

* fix(macos): align setup result initializer order

* fix(gateway): restore generic bootstrap retries

* chore(ui): record pairing startup budget

* chore(ui): refresh pairing startup budget

* style(gateway): format maintenance imports

* test(gateway): cover session-sharing mock

* fix(state): defer setup correlation schema

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-14 11:10:30 -07:00
Peter Steinberger 7fb47729c3 test: trim failover classifier duplicates (#123753)
* test: trim failover classifier duplicates

* test: prune failover max-lines baseline
2026-08-14 11:05:06 -07:00
Peter Steinberger 8c7e86a2ea fix(backup): reject unrestorable symlinks during create (#123672) 2026-08-14 06:55:48 -07:00
Peter Steinberger 946e49cb80 fix(ui): renaming a config map key that holds a stored secret dead-ends the draft or binds the wrong credential (#123571)
* fix(ui): block renaming config map keys that still hold redacted secrets

Renaming a map entry whose value is the server redaction sentinel moves
the sentinel to a new key. The gateway's exact-key restore then fails
closed forever (dead-end draft with a Retry that can never succeed), and
when the rename is folded with a delete of the target key inside one
autosave window, the restore silently binds the deleted entry's old
credential under the new name and drops the intended one — a green
Saved with the wrong secret.

The rename handler is the only owner that knows a sentinel was moved
rather than typed, so it now rejects the rename like the existing
duplicate-key guard and explains the retype path via input validity.
Plain values rename exactly as before.

* chore(ui): record startup JS growth for the redacted-rename guard string

The renameRedactedBlocked validity message in the startup en locale adds
65 gzip bytes over the recorded baseline; record the CI-measured size
per the established baseline pattern.
2026-08-14 06:34:24 -07:00
Josh Lehman f49eaf8639 fix(ci): prevent type-aware lint timeouts on constrained runners (#123328)
* oc-073.1: target UI style lint in changed checks

* oc-073.2: reuse tsgolint programs on hosted CI

* fix(ci): repair lint scope validation

* oc-073.3: restore no-target core lint fallback
2026-08-14 05:43:54 -07:00
Peter Steinberger ad400d810b fix(ci): record startup JS growth in the Control UI budget baseline
CI build-artifacts now measures 330521 B startup JS gzip, 14 B past
baseline+tolerance (329483+1024) after today's Control UI fixes. Every
PR building artifacts fails the budget check until the baseline records
the measured bytes; max committed baseline (358400 B) still bounds growth.
2026-08-14 05:10:12 -07:00
Peter Steinberger 860aeed8f6 feat(gateway): transfer node worker workspaces (#123280)
* feat(gateway): transfer node worker workspaces

* fix(gateway): harden node workspace transfer

* fix(gateway): isolate transfer HTTP contract

* fix(gateway): trim transfer HTTP exports
2026-08-13 21:00:44 -07:00
Peter Steinberger 438d035885 feat(gateway): launch workers on paired nodes (#123157)
* feat(gateway): launch workers on paired nodes

* docs(plan): track node worker launch wire

* fix(runners): restore node workspace ownership

Rehydrate durable workspace bindings after Gateway restart, fence timed-out node tunnel starts, and refuse incomplete clone fallback for submodule and Git LFS workspaces.

* test(runners): isolate node launch fixtures
2026-08-13 18:02:18 -07:00
Peter Steinberger 1348387076 refactor(plugins): replace node-llama-cpp with managed llama-server (#123105)
Move llama.cpp chat and local embeddings onto a verified externally managed llama-server runtime. Remove the in-process native runtime, forked embedding workers, and node-llama-cpp dependency while preserving guided setup, local GGUF models, tool-capable agent runs, diagnostics, and operator docs.
2026-08-13 16:58:20 -07:00
joshavant 1cb9f70cb3 fix(gateway): isolate full model catalog discovery 2026-08-13 18:57:50 -05:00
Peter Steinberger 12ada43464 refactor(ui): resolve duplicate stylesheet selectors and restore no-duplicate-selectors (#123166)
* refactor(ui): resolve duplicate stylesheet selectors and restore no-duplicate-selectors

* refactor(ui): merge duplicate .kbd block in file-preview modal instead of config carve-out
2026-08-13 12:50:18 -07:00
Peter Steinberger f2f3ba2eaf refactor(ui): consolidate responsive breakpoints onto a canonical ladder (#123160)
Use the 400, 560, 640, 768, 900, 1100, and 1320px max-width ladder. Round noncanonical thresholds up to the next rung so compact layouts engage before desktop layouts become cramped.

Move the 1180px agent-tool summary breakpoint to 1320px because the persistent desktop rail leaves about a 1020px content column at that viewport; 1100px would coincide with the rail disappearing and the content expanding.

Stylelint now enforces the ladder and the existing compound-query exceptions.
2026-08-13 12:34:50 -07:00
Peter Steinberger 843017073c refactor(ui): route stylesheet colors through the token layer and gate hex literals (#123156)
Add semantic, presentation, brand, and shared highlight tokens in base.css.
Remove dead fallbacks and consolidate dark/light syntax colors behind tokens.
Enforce color-no-hex for CSS while exempting token definitions and sprite artwork.
2026-08-13 05:36:52 -07:00