* fix(memory): keep canonical rows when same-file legacy memory tables diverge
* fix(memory): keep canonical-owned sources' chunk sets coherent during legacy import
* fix(memory): import legacy chunks when canonical source has no chunks
The same-file legacy migration excluded a legacy chunk from import whenever the
canonical index already had a source row for its (path, source). That stranded a
file whose canonical source was registered but had no chunks yet (indexing
interrupted before chunks were written, or embedding pending/failed): the legacy
chunks were its only searchable content, and the matching source hash stops sync
from re-indexing, so the file went silently unsearchable.
Re-key the chunk-coherence exclusion on canonical chunk ownership instead:
snapshot the (path, source) pairs that already have canonical chunks before the
import and skip legacy chunks only for those. A source with a canonical row but
no chunks now imports its legacy chunks. The snapshot is taken pre-insert because
the exclusion predicate reads the chunks table the import writes to.
Add regressions: legacy chunks import for a chunk-less canonical source while a
chunk-owning source still drops its stale legacy chunk; and restore abort
coverage for the meta and chunks copy assertions (previously only files was
exercised).
* fix(memory): harden same-file legacy conflict recovery
* fix(memory): rebuild ambiguous partial legacy sources
* fix(memory): reconcile migrated derived indexes
* fix(memory): close migrated index ownership gaps
* test(memory): align migration expectations
* test(tooling): match routed test order
* test(memory): exercise vector reload cleanup
* test(memory): prove real vector reload cleanup
* fix(memory): make migrated indexes converge
---------
Co-authored-by: Serhii Leniv <leniv.tech@gmail.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(scripts): reference update script for source-checkout gateway servers
Teams running a gateway from a git checkout kept re-deriving the same
update ritual by hand: restore the tracked bundle pnpm build rewrites,
fail closed on real local changes, fast-forward main or rebase a local
server branch, install deps, build clean (incremental builds have
shipped stale hashed chunks), restart the service. update-gateway.sh
encodes that as the documented reference; simple installs keep using
openclaw update --channel dev.
* fix(scripts): harden update-gateway guards and refresh docs map
Review findings: never run over a pre-existing rebase/merge/cherry-pick
(the abort path could discard operator progress), and treat untracked
files honestly — fail closed only where the clean build would delete
them, warn-list elsewhere. Regenerate docs_map for the new updating.md
section.
* fix(scripts): pipefail-safe warnings and honest disposable-dir policy
Review findings: head-truncating a long warning list through a pipe
dies under pipefail (SIGPIPE) in the warning path, so truncate via
here-strings; the untracked-under-build-dirs guard was theater since
ignored files bypassed it — declare dist/dist-runtime/.artifacts as
wholly disposable instead and say so; note the rm targets are
symlink-safe (no trailing slashes).
* fix(scripts): order guards before mutations and refuse symlinked build dirs
Review round: the in-progress-operation guard now precedes the bundle
restore (the script's first mutation), symlinked build dirs are refused
before the recursive clean (intermediate symlink resolution would
redirect the delete outside the checkout), and the untracked-files note
no longer overpromises — kept files can affect the build and the
message says so.
* fix(scripts): preserve merge commits when rebasing the server branch
Plain git rebase flattens merges, silently dropping merge-only conflict
resolutions from a local server branch; --rebase-merges keeps them.
The core-runtime-media-ui shard runs its ui config non-isolated for speed, but the
full chat-pane lifecycle tests instantiate the pane component, which relies on
chat-thread/chat-message module-level singletons (thread-state map, confirmation
dismisser WeakMap, module-scoped document context-menu listeners) and spies on those
modules. Under the shared non-isolated graph a stateful predecessor file can leave
those modules duplicated, so the pane binds to a different instance than the test's
spy/registry -- producing order-dependent flakes: `removeEventListener`-not-called
teardown assertions or 120s session-lifecycle hangs. Reproduced deterministically on
a Linux Node 24 Testbox (MAX_WORKERS=1); isolating a single file only shifted the
failure to a sibling pane test, so the whole full-pane family shares the fragility.
Route the 7 full-pane lifecycle test files through a new isolated jsdom lane
(vitest.ui-isolated.config.ts, isolate: true) for a fresh module graph; the other
~370 ui tests stay fast and non-isolated. Registered in both shard registries
(ci-node-test-plan.mjs, vitest.test-shards.mjs) and excluded from vitest.ui.config.ts.
Verified on Testbox: the deterministic single-worker media-ui shard goes from failing
to 5182 passed / 0 failed. Test-infrastructure only; no product code changes.
Zen (2%) breathes through a 6s cycle with one deliberate late snip,
drummer (1%) rocks a two-beat rhythm with jaw hits on the tilts, and
peekaboo (1%) ducks down small and pops back wide open. All three stay
in place (scale/rotate/vertical only); weights rebalance from the
default and southpaw stances.
The initials fallback span inside openclaw-viewer-avatar was hidden with a
single-class selector (.viewer-avatar__fallback, 0-1-0) that the later
.viewer-avatar > span display rule (0-1-1) outranks, so image avatars rendered
the photo and the colored initials side by side in the who's-online roster,
footer facepile, and session facepiles. Bump the hiding selector to
.viewer-avatar > .viewer-avatar__fallback so it wins again; the
.is-fallback swap (0-3-0) still shows initials when the image fails to load.
Regressed in #111421.
* refactor(gateway): declare spawn lineage explicitly at sessions.create
Spawn depth is now a declared fact, never inferred from parentSessionKey.
sessions.create accepts an optional spawnDepth (requires parentSessionKey) that
spawn-owned creations pass; every other fresh session persists spawnDepth 0,
making operator chats and UI forks spawn-capable roots. The visible spawn tool
declares callerDepth + 1, and subagent depth recovery no longer walks
parentSessionKey, which is UI threading only.
Accepted tradeoff (documented inline): pre-upgrade visible children whose only
lineage was parentSessionKey resolve as roots; the transient population may
spawn one extra generation, still capped by maxChildrenPerAgent.
* chore(protocol): regenerate Swift gateway models for sessions.create spawnDepth
* fix(channels): restore transcript context after restart
Merge bounded active-branch session transcript turns at the shared prepared-turn seam so message channels retain assistant replies after restart or history eviction. Migrate Telegram's one-off merge while preserving exact projection and legacy dedupe behavior.\n\nCloses #112520. Slack case reported by Joe Tam (@joetam) in #102594.
* style(channels): avoid spread in transcript mapping
* refactor(telegram): drop obsolete transcript exports
Media-facts program PR 4: legacy Media* fields are emitted only by the
user-turn transcript writer (persisted rows byte-identical, no
migration); reads normalize into ordered facts; documented singular
template variables project per attachment with per-attachment CLI
execution. Review fix: normalized attachments carry their original fact
index so sparse aligned slots cannot shift CLI url/type projections.
Ensure doctor runtime tool schema checks and wiki CLI resolve agent-scoped vaults from canonical agents.entries while retaining the internal list projection fallback.
Codex readers only reliably see the root AGENTS.md, so the .d.mts
declaration contract, the wrapper fail-loud convention, and the
stale-node_modules crash signature move from tribal knowledge into the
root file's Commands section.
Auto-parenting operator dashboard sessions to the agent main session (#110913)
collided with subagent depth recovery, which walks parentSessionKey as spawn
lineage for visible spawn children. Every WebChat dashboard session resolved to
spawn depth 1 and lost all sessions_spawn rights at the default maxSpawnDepth
of 1, breaking swarm collectors and the swarm dashboard from the web UI.
Record an explicit spawnDepth 0 on auto-parented dashboard creations so depth
recovery classifies them as roots; explicitly parented creations (visible
spawn children, forks) keep deriving depth from lineage.
* feat(ui): show live run output tokens
* fix(ui): map live usage to active runs
* test(ui): split live usage coverage
* fix(agents): scope live usage cleanup
* fix(agents): reject stale live usage events
* fix(agents): keep live usage type local
* refactor(ui): keep chat view within line budget
* fix(agents): require lifecycle for live usage
* refactor(ui): dedupe chat reply target type
Replace the onboarding security disclaimer's "hobby project ... sharp
edges" line with a friendlier attribution to the OpenClaw Foundation (a
non-profit), matching the LICENSE copyright holder, and drop the beta
claim. Rename the now-misnamed wizard.security.beta key to
wizard.security.attribution across en/zh-CN/zh-TW and its consumer.
README intro gains the same attribution plus "learns and grows with
you".
* chore(scripts): end failing oxlint runs with a stable status line
A crashed run-oxlint wrapper printed only a stack trace, and a lint
invocation whose output was truncated (cmd | tail -N) read as success —
which recently let a wrapper crash (stale node_modules after a dep-adding
merge) masquerade as a clean lint. Route the CLI entry through a small
wrapper that converts crashes into exit 1 and ends every failing run
with '[oxlint] FAILED (exit N)' as the final line.
* chore(scripts): declare runOxlintCliEntry in the script declaration contract
check-guards verifies .d.mts contracts against .mjs exports and
check-test-types consumes them; the new entry export needed both the
declaration and explicit log-parameter annotations in the test.
* fix(cron): validate failureAlert channel at the gateway boundary
`--failure-alert-channel` writes patch.failureAlert.channel, a field distinct from
delivery (own store columns, own delivery path in service/failure-alerts.ts). The
gateway validator only ran channel checks for delivery patches, so a
failure-alert-only patch stored an unknown channel unvalidated and only failed
later at delivery time with channel_not_found.
Add assertValidCronFailureAlert (reusing the announce-channel validation), wired
into create + update. It mirrors resolveFailureAlert() runtime resolution:
resolves the effective mode (job or global cron failure-alert mode; webhook alerts
skip channel-type validation), inherits the job delivery channel/target when the
alert names none (rejecting a routing-changing edit that would activate a
legacy-invalid inherited channel, while unrelated/threshold edits and already-
active alerts are not blocked), resolves a provider-prefixed target like the
delivery/failureDestination paths, and honors the channel:null last-fallback. Maps
failureAlert.channel errors to INVALID_REQUEST. Regression tests included.
Closes#103864
* fix(cron): align failure alert route validation
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(channels)!: shrink ChannelSetupInput to a generic envelope with a deprecated compatibility tier
* fix(channels): keep ChannelSetupInput structurally assignable without an index signature
* docs: regenerate docs map
* fix(onboard): survive inference owner drift and verify credentials route-aware
Guided onboarding crashed ("Could not start the CLI") on the openai-api-key
candidate when OPENAI_API_KEY was set and a Codex CLI login existed:
- owner revalidation resolved provider auth without the route model's
id/api, so the transport gate never applied and profile-first discovery
picked the Codex-imported ChatGPT OAuth profile instead of the env key
the probe ran with — a deterministic fingerprint mismatch
- that mismatch escaped the activation persistence path as a plain throw,
crashing the wizard ladder instead of moving to the next candidate
Owner re-resolution now carries resolved model transport facts (fail closed
when the model cannot be resolved), and all setup revalidation sites route
through one helper whose drift error activation maps to
{ ok: false, status: "auth" }.
* feat(onboard): prefer logged-in subscription CLIs over env API keys in setup ladder
* fix(onboard): promote codex only on verifiable ChatGPT OAuth tokens
* fix(onboard): surface detected AI candidates in the top auth-choice tier
* fix(onboard): name detected candidates and the current model clearly
* test(setup): mock model resolution in the bound-session lock test
* fix(system-agent): carry verified model transport facts
* fix(browser): tab creation steals window focus during agent automation
Agent-created tabs inherited CDP's foreground default: direct CDP
Target.createTarget omitted the background flag, and the extension
relay's createTab defaulted to active:true, so every agent tab open
activated the new tab (and, on the extension driver, focused the
window), interrupting whatever the human was doing in that browser.
Direct CDP tab creation now requests background:true (agent tab
ownership/selection is target-id based and never depended on
activation), and the extension relay defaults an omitted background
to true while preserving an explicit background:false, matching the
Codex/Claude-in-Chrome model the extension driver mirrors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(browser): keep focus fix LOC-neutral
Preserve background tab creation while keeping the oversized CDP and relay modules within the current LOC ratchet.\n\nCodex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* fix(browser): honor explicit CDP focus requests
Keep background-by-default automation while treating Target.createTarget focus=true as an explicit foreground request in the extension relay.
Codex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* fix(browser): preserve explicit CDP focus semantics
Apply the background-by-default automation policy only when focus is omitted, preserving focus=false foreground-tab requests as well as focus=true.
Codex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* fix(browser): preserve create target window focus
Carry the resolved CDP focus intent through the extension relay and explicitly focus the containing Chrome window when requested.\n\nCodex-Session: 019f5e93-780a-7350-88f9-1986cdb64914
* style(browser): refresh relay import order
* test(secrets): use secure node exec fixtures
* test(doctor): secure exec secret fixture
* test(doctor): retain narrowed temp path
* test(secrets): secure remaining exec fixtures
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
resolveComputerNode passed only the eligible (computer-capable) nodes to the
shared resolver, so an explicit exact node id belonging to an ineligible
device was never scored. Resolution could then fall through to display-name
matching and select a different eligible machine whose display name equaled the
requested id, observing and acting on the wrong desktop.
Mirror the mobile_ui-tool fix: match an exact node id against the full node
list first, reject an ineligible exact match with the eligible ids listed, and
only then resolve by display name among eligible nodes (still rejecting
ambiguous collisions, now with eligible ids in the error). Split the
node-selection tests into computer-tool.node-resolution.test.ts to keep the
main test file under max-lines.