* feat: add session discussion panel seam
* fix: keep discussion iframe cookie-capable and changelog release-owned
* test: cover cookie-capable discussion iframe sandbox
* fix: stretch discussion panel host so the embed fills the rail
* fix: keep provider failures retryable and probe discussion availability before showing the action
* fix: block same-origin discussion embeds, show action on catalog sessions, close stale panel on reconnect
* fix: scope discussion probes and panel callbacks to the issuing connection
* fix: dedupe in-flight discussion probes per session
* fix: retry superseded discussion probes and key-scope panel results
* fix: regenerate Swift protocol models and extend advertised-method expectations
* style: format chat-pane-header
* fix: regenerate Kotlin protocol models and date discussion methods in the 2026.7 train
* chore: restore release-owned changelog to main state
* chore: keep changelog untouched relative to merge-base
* feat(config): journal every config change with source labels and manual-edit detection
* fix(config): narrow journal exports and align plugin-write test expectations
* refactor(config): split journal snapshot store out of io.audit
* chore(config): keep journal snapshot context helpers module-local
* fix(plugins): roll back cleared runtime registrations when an activating reload aborts
loadOpenClawPlugins clears the process-global agent-harness, plugin-command,
compaction, detached-task, interactive-handler, embedding, memory-embedding,
and memory stores up front on an activating load and only re-registers them at
the end via activatePluginRegistry. The body has a finally with no catch, so a
throw during discovery or manifest load (a corrupted or half-written plugin
manifest mid-upgrade) leaves the previously active registry marked active while
its runtime stores are gone process-wide; the gateway reload driver only logs
and swallows the error.
Snapshot the process-global stores before the clear with the existing
snapshotPluginProcessGlobalState helper and restore them on any throw before
activation. clearActivatedPluginRuntimeState moves next to the snapshot and
restore helpers it pairs with in plugin-registration-transaction.ts, which
already owns atomic plugin registration state and imports from every module the
clear touches; loader.ts re-exports it so existing callers are unchanged.
* fix(plugins): make reload rollback activation-safe
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* Bound bundle command file reads with size cap
* fix: use buffer.toString for readRegularFileSync result
* fix: log oversized bundle command file diagnostic instead of silent skip
The catch block now captures the error and emits a console.warn with the file path and error detail, so upgrades do not silently remove oversized installed commands.
* test: verify oversized bundle command file is skipped and siblings continue
PR #110594: Add focused regression coverage:
- Test: an oversized bundle command markdown file (>1 MB) is skipped via catch + continue
- Test: normal sibling command files still load correctly
- Verifies console.warn diagnostic is emitted for the oversized file
* refactor(plugins): log rejected bundle commands
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
* style(plugins): format bundle warning
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ui): custom session icons with agent-drawable SVG and nav-parity pinned rows
* wip: slice-2 interleaved sidebar zone (pre-review)
* fix(ui): trim icon exports, bypass protocol barrel in startup bundle, regen Swift protocol client
* fix(ui): preserve unknown-agent zone entries on writes, prune entries on any unpin
* fix(ui): reset keeps unloaded session slots; enforce SVG byte cap on canonical form
* fix(ui): archiving a pinned session retires its sidebar zone slot
* fix(ui): persist drag-pinned zone slot only after the pin patch lands
* fix(ui): recompute drop insertion against freshest zone order on pin ack
* fix(ui): consume self-drop events before the zone bailout
* docs(ui): note the non-sidebar unpin pruning contract on pruneSidebarSessionEntry
* refactor(ui): extract pure session-tree projection; drop unused zone type export
* feat(chat): rewind and fork a session from a message bubble
* docs(web): document chat rewind and fork bubble actions
* fix(chat): lint cleanups, protocol regen, and test splits for rewind/fork CI
* fix(ui): fit chat rewind disabled styles inside the startup CSS budget
* fix(plugins): bound plugin manifest metadata file reads
* fix(plugins): use correct Error type check in oversized manifest catch
readRegularFileSync throws a plain Error (not RangeError) when the file
exceeds maxBytes. Change the catch-block type check from RangeError to
Error to properly detect oversized plugin manifests and emit the
subsystem warning.
* test(plugins): exercise valid oversized manifests
Co-authored-by: 陈宪彪0668000387 <chen.xianbiao@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): skip runtime plugin reload when a registry is already active
ensureRuntimePluginsLoaded rebuilt and activated a replacement plugin
registry on every call site invocation (agent turns, cron isolated-agent
runs, the gateway prewarm sidecar). Activating a replacement retires the
active registry and runs its plugin host cleanup, which cron.remove()s
persistent plugin-scheduled session turns before the replacement's async
re-registration catches up — silently deleting the cron row with only a
low-level 'cron service unavailable' WARN. Plugins using
api.session.workflow.scheduleSessionTurn for recurring jobs lose their
schedule on the next agent turn after boot.
Guard with getActiveRuntimePluginRegistry(): when a registry is already
active every caller's request is satisfied, so return before building a
replacement.
* fix(agents): make active-registry reuse compatibility-aware, not presence-only
The original guard (1de432a9) returned early whenever ANY registry was
active, without checking whether it was actually compatible with this
call's workspace, plugin scope, config, or runtime mode. A later call
requesting a different workspace could silently keep an unrelated
workspace's registry active instead of loading its own — a correctness
and workspace-isolation issue (caught in upstream review, PR #107752).
Fix: compute workspaceDir/startupPluginIds/loadOptions first, then check
getLoadedRuntimePluginRegistry({loadOptions, workspaceDir, requiredPluginIds})
before deciding to skip — reusing the existing loader cache-key
compatibility rules (workspace, config, runtime subagent mode, required
plugin IDs) that already govern normal cache-hit reuse elsewhere. Only
skips the reload when an active registry is genuinely compatible; still
loads a fresh one otherwise.
Tests: replaced the presence-only regression test with three cases —
compatible-registry reuse (skip), incompatible/different-workspace (still
loads), and startup-scoped plugin IDs passed through to the compatibility
check. All 5 acceptance criteria from the review verified locally:
runtime-plugins.test.ts, active-runtime-registry.test.ts,
standalone-runtime-registry-loader.test.ts, and the full cron lane
(33 files / 460 tests, including run.runtime-plugins.test.ts) all green;
pnpm build completes clean (5m14s, no errors).
* fix(plugins): preserve live scheduled turns
Scope dynamic scheduler cleanup to the retiring registry owner and keep runtime workflow APIs callable while their registry remains pinned and live.\n\nCo-authored-by: Dan Scarafoni <dan@poweredbyzon.com>
* test(plugins): prove recurring cron ownership
* chore(plugin-sdk): refresh api baseline
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(plugins): handle NaN from Date.parse in catalog rollback detection
isHostedCatalogSignedFeedRollback compared generatedAt timestamps using
Date.parse without checking for NaN. An invalid date string in the
candidate feed would produce NaN < validNumber === false, silently
accepting the candidate instead of treating it as a potential rollback.
Now, if the candidate's generatedAt is invalid, we conservatively treat
it as a rollback. If the current feed's generatedAt is invalid, we
treat the candidate as newer (not a rollback).
* fix(plugins): handle NaN from Date.parse in snapshot store monotonic rollback
isMonotonicRollback in official-external-plugin-catalog-snapshot-store.ts
had the same NaN comparison bug as isHostedCatalogSignedFeedRollback:
Date.parse(candidate.generatedAt) < Date.parse(current.generatedAt)
returns false when either timestamp is invalid, silently accepting a
candidate that should be rejected as a rollback.
Mirror the catalog.ts fix: if the candidate's generatedAt is invalid,
conservatively treat it as a rollback; if the current snapshot's
generatedAt is invalid, treat the candidate as newer (not a rollback).
Export __testing for isMonotonicRollback and add five regression tests
covering newer/older sequence, equal-sequence timestamp comparison, and
both invalid-timestamp branches.
* fix(plugins): export rollback guards directly instead of __testing wrapper
The knip production deadcode scan flags __testing as an unused export
because test files are not entry points in that mode. Export
isHostedCatalogSignedFeedRollback and isMonotonicRollback as named
exports instead of wrapping them in __testing objects. Both are already
called from production code, so knip will see them as used.
* fix(plugins): keep rollback guards as internal functions, remove unit tests
The knip production deadcode scan flags any export that is only
referenced by test files as unused, even if the function is called
within the same source file. Both isHostedCatalogSignedFeedRollback
and isMonotonicRollback are internal helpers called from production
code in the same module, so exporting them solely for test access
triggers the check-dependencies CI gate.
Revert both functions to private (non-exported) and remove their
direct unit tests. The NaN-handling fix is still validated by the
existing catalog snapshot integration tests that exercise rollback
detection through the public API.
* fix(plugins): remove stray blank line before max-lines disable comment
* fix(plugins): validate catalog feed timestamps
* fix(plugins): validate recovered catalog sequence
---------
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>