fix(memory): keep active memory capability visible across plugin scopes#69458
fix(memory): keep active memory capability visible across plugin scopes#69458rzyns wants to merge 20 commits into
Conversation
…-instrumentation Plugins: add plugin scope debug logs
Before this change `memoryPluginState.capability` was stored inside the scope-swapped plugin state, so activating a plugin scope that did not include the memory plugin (via `clearMemoryPluginState` + `restoreMemoryPluginState`) would wipe the active capability globally. The memory-wiki bridge then observed `capabilityPluginId: null` and could not read memory-core's public artifacts, so wiki ingestion saw zero workspaces. Hoist `activeMemoryCapability` into its own module-level var so it lives outside the per-scope snapshot. `clearMemoryPluginState` and `restoreMemoryPluginState` no longer touch the capability; a new `clearActiveMemoryCapability` teardown hook is exposed and `_resetMemoryPluginState` (test-only) calls both helpers. The loader's `CachedPluginState.memoryCapability` cache field is removed since the capability is no longer scope-bound. Adds regression tests that register a memory capability in one scope and load a second scope (different cacheKey, memory plugin excluded), then assert `listActiveMemoryPublicArtifacts` still returns the artifacts. Fixes OC-40.
Greptile SummaryThis PR fixes OC-40 by hoisting Confidence Score: 5/5Safe to merge; the core fix is correct and well-tested, remaining findings are P2 style/cleanup concerns. The architectural change is sound — moving Review the debug probe scaffolding in
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82768d82a2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| restoreMemoryPluginState({ | ||
| capability: cached.memoryCapability, | ||
| corpusSupplements: cached.memoryCorpusSupplements, | ||
| promptBuilder: cached.memoryPromptBuilder, | ||
| promptSupplements: cached.memoryPromptSupplements, |
There was a problem hiding this comment.
Restore active memory capability on cached plugin loads
The cache-hit path now restores only legacy memory state and returns without restoring the scope’s memory capability, so switching between cached scopes can leave activeMemoryCapability pointing at the wrong plugin. Concretely, if scope A (memory plugin A) is cached, then scope B (memory plugin B) loads, a later cache hit for scope A will keep B’s capability because no capability is rehydrated here before return; calls like buildMemoryPromptSection, getMemoryRuntime, and listActiveMemoryPublicArtifacts can therefore use the wrong memory implementation for the active cache key.
Useful? React with 👍 / 👎.
Addresses follow-ups from PR openclaw#69458 code review: - Strip temporary `[memory-core-register-probe]` step wrapper in `extensions/memory-core/index.ts` (removes try/catch control-flow change that existed purely for debug). - Strip `[memory-core-artifacts-probe]` in `extensions/memory-core/src/public-artifacts.ts`. - Strip `[memory-bridge-probe]` in `src/plugins/memory-state.ts#listActiveMemoryPublicArtifacts`. - Restore strict `publicArtifacts.listArtifacts` invocation via a local bind — narrower than `cap?.capability.publicArtifacts !== undefined` and satisfies oxlint `unbound-method` because the reference is read into a const before the optional call. The structured `[plugin-scope-debug]` diagnostic in `src/plugins/loader.ts` stays — it has its own sanitization, tests, and is the long-term entry point for scope diagnostics. Co-Authored-By: Claude Opus 4.7 <[email protected]>
…eiver Extracting listArtifacts into a local const stripped the `this` binding; plugins implementing the method-shorthand type with `this`-bound state would have run with `this === undefined`. Calling via `provider?.listArtifacts(params)` keeps the receiver bound while still satisfying oxlint's unbound-method rule. Flagged by Codex review on PR #3. Co-Authored-By: Claude Opus 4.7 <[email protected]>
refactor(memory): clean up OC-40 diagnostic probes
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4bfed6291a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // artifacts even when memory-core was loaded under a different cache key). | ||
| // Keeping it outside `memoryPluginState` means `clearMemoryPluginState` / | ||
| // `restoreMemoryPluginState` do not disturb it. See OC-40. | ||
| let activeMemoryCapability: MemoryPluginCapabilityRegistration | undefined; |
There was a problem hiding this comment.
Scope active memory capability per activation context
activeMemoryCapability is now process-global and is only cleared by the test-only _resetMemoryPluginState(), so normal plugin reloads can keep a stale memory capability when no memory plugin re-registers (for example after setting plugins.slots.memory to "none" or when the selected memory plugin fails to load). In that state, getMemoryRuntime, buildMemoryPromptSection, and listActiveMemoryPublicArtifacts continue using the previous plugin even though the new activation no longer selected it, which makes memory behavior inconsistent with current config.
Useful? React with 👍 / 👎.
Resolves conflict in src/plugins/loader.test.ts preserving both regressions: OC-40 (memory capability survives scope swaps excluding memory plugin) and upstream (previously registered memory capability survives activate:false snapshot loads). Fixes two slots.memory: null spots in test helpers — the slot type is now string | undefined, so use the "none" sentinel to opt out. Includes Greptile P2 fix in src/plugins/loader.ts: clearPluginLoaderCache now calls clearActiveMemoryCapability to clear the hoisted capability on full loader resets (used for test isolation, not scope swaps). Pre-existing Telegram AbortSignal typecheck at extensions/telegram/src/bot.ts:193,195 is unrelated to this merge; --no-verify used per authorization for that specific known failure.
|
Follow-up merge pushed (3165989):
Verification (clean env,
Pre-commit hook surfaced only the known pre-existing Telegram |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1bf4c5a7d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| restoreMemoryPluginState({ | ||
| capability: previousMemoryCapability, | ||
| corpusSupplements: previousMemoryCorpusSupplements, | ||
| promptBuilder: previousMemoryPromptBuilder, | ||
| promptSupplements: previousMemoryPromptSupplements, |
There was a problem hiding this comment.
Restore memory capability on failed plugin registration
If a memory plugin calls registerMemoryCapability(...) and then throws during register, this rollback restores only legacy memory state and leaves activeMemoryCapability mutated by the failed plugin. That means subsequent memory prompt/runtime/public-artifact calls can run against a plugin that never successfully loaded. The rollback needs to snapshot and restore capability state alongside the other previousMemory* values.
Useful? React with 👍 / 👎.
| const memorySlot = context.activationSourceConfig.plugins?.slots?.memory?.trim(); | ||
| if (!memorySlot) { | ||
| return scopedPluginIds; | ||
| } | ||
| return [...new Set([...scopedPluginIds, memorySlot])]; |
There was a problem hiding this comment.
Skip disabled memory slot when building CLI plugin scope
This appends the raw plugins.slots.memory value from activation config without normalizing sentinel values. When users set slots.memory: "none", "none" is added to onlyPluginIds; if a plugin with id none is present, command-scoped CLI loading will unexpectedly activate it even though memory was explicitly disabled. Normalize this slot value the same way as runtime config (none => disabled) before adding it to the scoped plugin list.
Useful? React with 👍 / 👎.
Conflict with openclaw#69537 (perf(plugin-sdk): per-phase + per-jiti-call probes for bundled channel entries), which extracted the file-local `shouldProfilePluginLoader` / `profilePluginLoaderSync` helpers into a new shared module `src/plugins/plugin-load-profile.ts` (exposed as `withProfile`). Our branch added the plugin-scope-debug helpers just above the now-removed profile functions, so the regions overlapped. Resolution: keep the plugin-scope-debug helpers (still consumed by emitPluginScopeDebugLog call sites in this file); drop the file-local profile helpers in favour of the shared `withProfile` import (already pulled in by the merge). Verified: - pnpm tsgo (core) — 0 errors - pnpm test src/plugins/loader.test.ts — 104/104 Committed with --no-verify: pre-commit hook ran tsgo:extensions (public plugin contract triggers extensions lane) and flagged 2 pre-existing TS2739 errors in extensions/telegram/src/bot.ts (cross-realm AbortSignal type divergence between grammY node-fetch types and Node globals, noted in the comment at the call site). Errors pre-date this merge; unrelated to the conflict resolution.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eec662d943
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| restoreMemoryPluginState({ | ||
| capability: previousMemoryCapability, | ||
| corpusSupplements: previousMemoryCorpusSupplements, | ||
| promptBuilder: previousMemoryPromptBuilder, | ||
| promptSupplements: previousMemoryPromptSupplements, |
There was a problem hiding this comment.
Restore memory capability after activate:false snapshot loads
The activate:false path is intended to be read-only, but this restore block now only reinstates legacy memory state and leaves activeMemoryCapability as whatever the snapshot load's plugin registration last set. If a snapshot load (for example, provider/status discovery paths that call loadOpenClawPlugins with activate:false) touches a memory plugin, it can silently change the process-global memory capability even though no activating reload occurred, so later calls like getMemoryRuntime, buildMemoryPromptSection, or listActiveMemoryPublicArtifacts can run against the wrong plugin.
Useful? React with 👍 / 👎.
…ty-workspace-scope
|
Related work from PRtags group Title: Open PR duplicate: memory capability lost across plugin scopes
|
|
Thanks for the detailed repro work. The Memory Wiki bridge 0-artifacts/0-workspaces path has been closed by #72615, which routes the affected CLI commands through the Gateway runtime and adds focused bridge/CLI regressions. Closing this broader plugin-scope capability approach as superseded for the now-closed bridge issue. |
Summary
activeMemoryCapabilityout of the scope-swappedmemoryPluginStateso a plugin scope that does not include the memory plugin no longer wipes the capability globally — fixes the memory-wiki bridge seeingcapabilityPluginId: nulland reporting zero workspaces/artifacts.clearActiveMemoryCapabilityteardown;_resetMemoryPluginState(test-only) now calls both it andclearMemoryPluginState. Remove the now-unusedCachedPluginState.memoryCapabilityfield from the loader.cacheKeyandonlyPluginIdsthat excludes the memory plugin, assertlistActiveMemoryPublicArtifactsstill returns the artifacts.Fixes OC-40.
Test plan
src/plugins/memory-state.test.ts— 14/14 passsrc/plugin-sdk/memory-host-core.test.ts— passsrc/plugins/registry.dual-kind-memory-gate.test.ts— passextensions/memory-wiki/src/bridge.test.ts— 6/6 passsrc/plugins/loader.test.ts— 97/99 pass (the 2 failing tests —sanitizes plugin scope debug…,suppresses trust warning logs…— reproduce onmainwithout this change, so they are pre-existing flakes unrelated to this PR)pnpm tsgo:core+pnpm tsgo:core:testpassopenclaw wiki bridge import --jsonagainst a live workspace and confirmworkspaces > 0, artifactCount > 0🤖 Generated with Claude Code