* fix: release stale reply work during reset cleanup
* fix: release archived reply runs during reset
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* [AI] fix(memory): add batch completed and batch failed logs for embedding ops
The embedBatchWithRetry function logged 'batch start' but never logged
'batch completed' or 'batch failed' after the embedding batch call,
leaving operators with no post-request feedback. When batches hang or
time out, only 'batch start' appears in logs with no diagnostic signal.
Add 'batch completed' log after runEmbeddingOperationWithTimeout success
and 'batch failed' log in the catch handler. This is an observability
improvement, not a functional fix for the underlying hang (#93312).
Related to #93312
* [AI] fix(memory): use formatErrorMessage for embedding batch error log
Replace String(err) with formatErrorMessage(err) in the batch failed
catch handler to redact sensitive provider error text (e.g. API keys
and tokens embedded in error messages) before logging.
Related to #94732
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(openai): treat a config apiKey as configured for image generation
The openai image provider's isConfigured only consulted env vars / auth
profiles (isProviderApiKeyConfigured) and considered a config apiKey only
inside that env/profile-gated branch. A provider apiKey supplied directly in
config (models.providers.openai.apiKey) — e.g. an AI-gateway token alongside a
custom baseUrl — was reported as not-configured, even though the generate path
resolves exactly that credential via resolveApiKeyForProvider and honors the
config baseUrl. This made image generation behave differently from chat models,
which authenticate purely from provider config.
Recognize a config apiKey as configured so image generation works purely from
config, like chat, with no OPENAI_API_KEY env var or auth profile. Env/profile
and Codex/ChatGPT-OAuth branches are unchanged. Formatting verified with oxfmt
--check (no node_modules in this worktree); full tests run in CI/Testbox.
* fix(openai): require a non-empty config apiKey for image readiness
* fix(openai): normalize config apiKey readiness via hasConfiguredSecretInput
---------
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
* fix(compaction): annotate partial summaries with chunk order and source time range labels
In summarizeInStages, partial summaries are wrapped into synthetic
AgentMessage objects before the final merge pass. Previously each
synthetic message used Date.now() as its timestamp and no ordering
metadata was embedded in the content. Since serializeConversation
(in agent-core) only preserves text content and discards timestamps,
the LLM merger could not distinguish which summary chunk represented
older vs newer history, making the "PRIORITIZE recent context" merge
instruction ineffective.
Fix:
1. Embed a chronological label with source time range in each summary
content text (e.g. "[Chunk 1 - oldest messages [2026-07-05 14:00]]").
2. Extract actual timestamp ranges from original chunk messages via new
extractChunkTimeRange() helper.
3. Assign ascending timestamps from a single Date.now() capture so
relative order is preserved for code paths reading timestamp field.
4. Update test to verify merge messages receive the labels.
5. Add real behavior proof script (proof/issue-100636-chunk-labels.ts)
with 10/10 verification checks.
Fixes#100636
* fix(compaction): stabilize chunk time labels
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Adds an additive worktree flag to sessions.create so any new chat can run in an
isolated managed worktree of the agent's git workspace, with the branch checked
out and .worktreeinclude provisioning applied. The session's spawnedCwd points at
the matching subdirectory inside the worktree so chat runs, CLI, and the file
browser execute there. agents.list gains workspaceGit (workspace or an ancestor
is a git checkout) to gate the affordance; web sidebar, iOS, and Android expose a
New-Chat-in-worktree action. Uses the method's operator.write scope, but the
.openclaw/worktree-setup.sh step runs only for operator.admin callers since it
executes repo code. Deleting the session, or leaving via a plain New Chat, clears
the cwd and lossless-removes the worktree; idle GC treats recent session activity
as worktree activity so an active session's checkout is never swept.
Live-verified end-to-end on a real gateway; follow-up to #100535 (issue #100534).
Nav routes (Overview, Workboard, Agents, More) now render first and stay
pinned; the session list owns the remaining sidebar height and scrolls
internally instead of being capped at min(42vh, 400px). Mobile drawer
matches the pinned-nav layout.
* feat(browser): restore driver "extension" via loopback Chrome extension relay
Reintroduces browser profile driver "extension" (removed in 2026.3.22) as a
loopback relay that drives the user's signed-in Chrome through an MV3 extension
instead of the remote-debugging port. This avoids Chrome's blocking "Allow
remote debugging?" prompt, which cannot be clicked when the operator drives
OpenClaw from a phone. Automated tabs live in an "OpenClaw" tab group (the
consent boundary), mirroring the Codex/Claude-in-Chrome model.
- relay bridge synthesizes the CDP browser target surface for Playwright
connectOverCDP and forwards session-scoped commands to chrome.debugger
- relay server binds loopback only; both sides authenticate with a token
derived (HMAC-SHA256) from gateway auth, so the raw credential never reaches
Chrome; extension origin + loopback Host checks guard the upgrade
- built-in "chrome" profile; distinct relay ports per extension profile;
relay reconciles on auth rotation / cdpPort change and prunes removed profiles
- doctor + status surface the extension transport; doctor keeps repairing the
retired relay endpoint URL on legacy "extension" profiles
Refs #53599
* feat(browser): bundle the OpenClaw MV3 Chrome extension
Thin MV3 extension (chrome-extension/): a WebSocket client to the loopback
relay plus chrome.debugger forwarding and OpenClaw tab-group management. All
CDP target synthesis lives server-side in the relay bridge, so the extension
stays a dumb transport (the removed 2026.3 extension put that logic in a
1000-line untestable service worker). Popup handles pairing and per-tab share
toggle; `openclaw browser extension path|pair` load and pair it. A build copy
hook stages it into dist so the load path is stable.
Refs #53599
* docs(browser): document the Chrome extension profile
Adds docs/tools/chrome-extension for the restored extension driver (install,
pair, tab-group consent model, security posture) and wires it into the browser
docs profile section and nav.
Refs #53599
* feat(browser): make the extension relay work on remote browser nodes
Derives the relay auth token from a host-local secret in the credentials dir
(created on first use) instead of gateway auth. Each machine that runs a
browser — the gateway host and every browser node host — owns its own token, so
the extension pairs with whichever machine hosts its Chrome and no gateway
credential travels to a node. The node host already runs the shared browser
control bootstrap, so this is all that was missing for cross-machine control.
Also removes the "relay needs gateway auth before it can start" failure mode:
startup and `openclaw browser extension pair` ensure the secret exists.
Refs #53599
* fix(browser): harden relay secret creation and satisfy CI lint/typecheck
- Make the host-local relay secret creation atomic (O_CREAT|O_EXCL + adopt the
winner on EEXIST) so the gateway service and `extension pair` CLI cannot mint
divergent tokens on a fresh host (would 401 until restart); credentials dir
created mode 0700. (adversarial review finding)
- Resolve type-aware oxlint findings across the relay + extension: unknown catch
vars, addEventListener over ws.on* in the MV3 worker, void async listeners,
drop useless returns/spreads, Object.assign over map-spread, safe ws frame
decode (Buffer[]/ArrayBuffer), toSorted.
- Add extensionRelayDefaultPort/extensionRelayPorts to remaining test config
literals; type the extension relay-core module (.d.ts, excluded from dist);
regenerate docs_map.
* fix(browser): satisfy OpenGrep security policy on the relay
- Hash both operands before timingSafeEqual so token comparison has no
length short-circuit (GHSA-JJ6Q-RRRF-H66H).
- Bound the relay WebSocketServer with maxPayload (64 MiB, headroom for CDP
screenshots/bodies) against oversized frames (GHSA-VW3H-Q6XQ-JJM5).
- Rewrite the config test env helper to avoid the skill-env-host-injection
shape (GHSA-82G8-464F-2MV7); it is a test-only env swap.
* fix(plugins): discover config load.paths plugins when index has entries
Installing a managed npm plugin populates the persisted plugin index.
Once the index has entries (index.plugins.length > 0),
loadPluginManifestRegistryForInstalledIndex is used instead of the
full discovery path. This path only processes plugins that already
exist in the index — if workspace plugins from config plugins.load.paths
are missing from the index, they are silently dropped from the manifest
registry, producing 'plugin not found (stale config entry ignored)'
warnings on gateway startup and in CLI commands.
Fix: after converting index records to plugin candidates, also
discover plugins from config plugins.load.paths and merge any
candidates that are not already represented in the index-based
list. This ensures config-origin workspace plugins are always
included in the manifest registry regardless of index completeness.
Fixes#99185
* fix(plugins): address clawsweeper review — config-only scope, pluginId scoping, regression tests
- P1: Limit extra discovery scope to config-origin candidates only
(filter by origin === 'config') instead of full discoverOpenClawPlugins
- P2: Preserve pluginId scoping for load-path candidates
(filter extra candidates by pluginIdSet when present)
- Add 3 regression tests: load.paths discovery, empty-load no-op,
pluginId scoping preservation
- Add L2 real behavior proof with direct function call evidence
🦞 diamond lobster: L2 evidence (real function call + objects)
Ref. https://github.com/openclaw/openclaw/pull/99196
* fix(plugins): replace discoverOpenClawPlugins with config-only discoverFromConfigPaths
P1: The installed-index fallback previously called discoverOpenClawPlugins,
which scans bundled/global/workspace roots and returns their diagnostics.
This allowed unrelated shared-root diagnostics to leak into the
installed-index config validation surface.
Replace it with discoverFromConfigPaths — a new helper that scans only
the user-configured plugins.load.paths entries via discoverFromPath
directly, producing zero bundled/global candidates or diagnostics.
P2: pluginId scoping is preserved for load-path candidates.
Added discoverFromConfigPaths to src/plugins/discovery.ts to keep the
config-only discovery path reusable and explicit.
🦞 diamond lobster: L2 evidence (terminal output from real dev build)
Ref. https://github.com/openclaw/openclaw/pull/99196
* fix(plugins): preserve bundled-load-path alias filtering in discoverFromConfigPaths
P2: The new discoverFromConfigPaths helper (added in Round 1 to fix the
P1 full-discovery issue) bypassed normal discovery's bundled-load-path
alias guard. If a user had a bundled plugin directory in their
plugins.load.paths, the installed-index fallback would treat it as a
valid config-origin candidate and potentially override/duplicate bundled
plugins.
Add resolvePackagedBundledLoadPathAlias check in discoverFromConfigPaths
before calling discoverFromPath, so bundled paths are skipped with a
warning diagnostic matching normal discovery behavior.
Add P2 regression test verifying bundled plugin load paths are ignored
on the installed-index path while real workspace load paths still work.
Ref. https://github.com/openclaw/openclaw/pull/99196
* fix(plugins): forward load-path diagnostics unconditionally in installed-index path
Previously, diagnostics from discoverFromConfigPaths were only forwarded
when at least one extra candidate survived filtering. Configs whose
plugins.load.paths contained only bundled aliases, missing paths, duplicates
already in the index, or scoped-out entries silently lost the normal
discovery warning or doctor hint.
Now extraDiagnostics is assigned outside the extraCandidates.length > 0
guard, so every config load-path diagnostic reaches the caller regardless
of whether a new candidate is merged.
Adds a focused regression test verifying that a bundled-only load path
still surfaces the expected alias-guard warning diagnostic.
🦞 diamond lobster: P2 fix, no new L2 evidence needed (regression test
covers the diagnostic-forwarding contract directly)
Ref. https://github.com/openclaw/openclaw/pull/99196
* fix(plugins): preserve requiresPlugins diagnostics for load paths in installed-index path
- [P2] Export addMissingRequiredPluginDiagnostics from discovery.ts for reuse
- [P2] Call addMissingRequiredPluginDiagnostics on combined index + load-path candidates before passing to loadPluginManifestRegistry
- [P2] Add regression tests: warns when load-path plugin requires missing plugin, does not false-warn when required plugin is already in index
🦞 diamond lobster: L2 + test coverage
Ref. https://github.com/openclaw/openclaw/pull/99196
* fix(plugins): add missing discovery.js mock exports for addMissingRequiredPluginDiagnostics
plugin-install.test.ts mocks discovery.js but the mock didn't include the
newly exported addMissingRequiredPluginDiagnostics, causing CI failures in
tests that exercise loadPluginManifestRegistryForInstalledIndex.
Also add discoverFromConfigPaths to the mock (already imported but was
only latent because the mock's code path wasn't triggered with load paths).
Ref. https://github.com/openclaw/openclaw/pull/99196
* fix(plugins): rebuild stale configured plugin indexes
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>