Skip to content

fix(memory-wiki): route wiki bridge import CLI through gateway RPC#67208

Closed
moorsecopers99 wants to merge 2 commits into
openclaw:mainfrom
moorsecopers99:fix/memory-wiki-cli-bridge-import
Closed

fix(memory-wiki): route wiki bridge import CLI through gateway RPC#67208
moorsecopers99 wants to merge 2 commits into
openclaw:mainfrom
moorsecopers99:fix/memory-wiki-cli-bridge-import

Conversation

@moorsecopers99

Copy link
Copy Markdown

Summary

  • Problem: openclaw wiki bridge import always returns 0 artifacts, even when openclaw gateway call wiki.bridge.import against the same store returns the real count. openclaw wiki doctor then falsely warns that memory-core is not exporting any public artifacts.
  • Why it matters: users (and the doctor report) conclude their bridge configuration or dreaming pipeline is broken when it is actually fine. The CLI / gateway-RPC mismatch is hard to diagnose without reading the plugin registration code.
  • What changed: runWikiBridgeImport now calls the wiki.bridge.import gateway RPC via callGatewayFromCli instead of invoking syncMemoryWikiImportedSources in-process. addGatewayClientOptions is attached to the wiki bridge import subcommand so users can point at a remote gateway or pass an explicit token. Error path surfaces a clear "gateway running?" message instead of a silent 0.
  • What did NOT change (scope boundary): other wiki CLI commands (wiki status, wiki doctor, wiki unsafe-local import) still run in-process and are out of scope for this PR. The gateway RPC handler, the sync implementation, and memory-core's capability registration are all untouched.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: registerMemoryCapability — which populates memoryPluginState.capability.capability.publicArtifacts.listArtifacts — runs inside memory-core's register() hook, which only fires in the gateway daemon process. The CLI command path loads each plugin's cli-metadata.js to wire subcommands but does not execute the full plugin register() flow. Result: in a fresh CLI subprocess, memoryPluginState.capability is undefined and listActiveMemoryPublicArtifacts short-circuits to []. The gateway RPC handler wiki.bridge.import works because it runs inside the daemon where the capability is already registered.
  • Missing detection / guardrail: no test covered the "CLI subprocess → memory-core capability" boundary, so the in-process path silently returned an empty result instead of failing loudly.
  • Contributing context (if known): listActiveMemoryPublicArtifacts intentionally degrades gracefully to [] when the capability is absent — that is correct for plugin-aware callers inside the daemon, but catastrophic for a CLI entrypoint expecting artifacts.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/memory-wiki/src/cli.test.ts (new describe("runWikiBridgeImport", ...) block).
  • Scenario the test should lock in: runWikiBridgeImport must call callGatewayFromCli("wiki.bridge.import", ...) with forwarded opts and render Bridge import synced N artifacts...; it must throw a clear "gateway running?" error when the RPC returns nothing.
  • Why this is the smallest reliable guardrail: the bug is about which code path the CLI takes, not about the sync implementation itself. A unit test with an injected callGateway mock catches any future regression that reroutes the command back to the in-process sync helper.
  • Existing test that already covers this (if any): None — prior to this PR, runWikiBridgeImport had no direct test.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • openclaw wiki bridge import now reports the same importedCount / artifactCount as openclaw gateway call wiki.bridge.import for the same store.
  • openclaw wiki bridge import --help now lists --url, --token, --timeout, --expect-final in addition to --json (from addGatewayClientOptions).
  • When the gateway is not running, the CLI now errors loudly with a WebSocket connection failure (and a "gateway running?" hint if the RPC returns an empty response) instead of silently printing 0 artifacts.
  • No config, no persisted state, no protocol changes.

Diagram (if applicable)

Before:
[openclaw wiki bridge import] -> [CLI subprocess]
                              -> [syncMemoryWikiImportedSources] (in-process)
                              -> [listActiveMemoryPublicArtifacts]
                              -> [memoryPluginState.capability = undefined]
                              -> [returns []] -> "0 artifacts" (silent false negative)

[openclaw gateway call wiki.bridge.import] -> [gateway daemon]
                                           -> [syncMemoryWikiImportedSources]
                                           -> [capability registered] -> "N artifacts"

After:
[openclaw wiki bridge import] -> [CLI subprocess]
                              -> [callGatewayFromCli("wiki.bridge.import")]
                              -> [gateway daemon RPC handler]
                              -> [syncMemoryWikiImportedSources]
                              -> [capability registered] -> "N artifacts"

[openclaw wiki bridge import] with gateway down -> [WebSocket connection error] (loud failure)

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No — the new --token flag on the subcommand uses the same addGatewayClientOptions surface already used by other CLI commands (browser, qa-lab).
  • New/changed network calls? Yes, but same target: the CLI now opens a WebSocket to the local gateway instead of calling the in-process sync helper. The RPC target, scope, and payload are identical to the existing wiki.bridge.import method that has been shipped for prior releases.
  • Command/tool execution surface changed? No
  • Data access scope changed? No — the RPC handler uses WRITE_SCOPE, same as before. Anyone who could already run the CLI locally already had that authority.

Repro + Verification

Environment

  • OS: Linux (also reproduced on Debian 13 in the issue)
  • Runtime/container: local pnpm openclaw gateway --force, memory-wiki extension bundled
  • Model/provider: N/A (CLI only)
  • Integration/channel (if any): memory-core bridge mode (vaultMode: "bridge", all bridge flags enabled)
  • Relevant config (redacted): plugins.entries.memory-wiki.config.vaultMode = "bridge"

Steps

  1. Start gateway: pnpm openclaw gateway --force
  2. Run pnpm openclaw wiki bridge import --json
  3. Compare to pnpm openclaw gateway call wiki.bridge.import --json
  4. Stop the gateway and re-run pnpm openclaw wiki bridge import to exercise the failure path

Expected

  • Step 2 and step 3 print identical importedCount / artifactCount
  • Step 4 prints a clear WebSocket/"gateway running?" error instead of 0 artifacts
  • pnpm openclaw wiki bridge import --help lists --url, --token, --timeout, --expect-final

Actual

  • Matches expected. Verified via unit tests that mock callGatewayFromCli and assert both the happy path and the empty-result error path (extensions/memory-wiki/src/cli.test.ts).

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)
$ pnpm test extensions/memory-wiki/src/cli.test.ts
 Test Files  1 passed (1)
      Tests  6 passed (6)

$ pnpm check
... (tsgo, oxlint, import-cycles, madge-import-cycles, host-env-policy all green)

Human Verification (required)

  • Verified scenarios:
    • runWikiBridgeImport now routes through the mocked callGatewayFromCli with "wiki.bridge.import" as the method and forwards url / token / json opts
    • Renders Bridge import synced N artifacts across W workspaces (...) output from the RPC result
    • Throws a "gateway running?" error when the RPC returns undefined
  • Edge cases checked:
    • Gateway RPC opts come through the Commander subcommand (WikiBridgeImportCommandOptions now extends GatewayRpcOpts)
    • Empty/missing gatewayOpts still works (spread handles undefined)
    • Other wiki CLI commands that share syncMemoryWikiImportedSources are unchanged
  • What you did not verify:
    • Live gateway round-trip against a store with real dreaming-narrative memory artifacts — the local test store had no indexed artifacts. Unit tests cover the routing contract; the live end-to-end path is identical to the already-working gateway call wiki.bridge.import the issue author validated.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? YesrunWikiBridgeImport keeps its exported signature (new fields are optional), and the command name / output format are unchanged.
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Users who ran openclaw wiki bridge import without a running gateway used to get a silent 0 artifacts result; they will now get a loud WebSocket error.
    • Mitigation: That prior behavior was the bug. The loud error is the correct outcome and matches how callGatewayFromCli already behaves in other CLI commands (browser, qa-lab). The error message explicitly hints at the gateway.
  • Risk: Test-only injection seam (callGateway? param) leaks into the public signature of runWikiBridgeImport.
    • Mitigation: Field is optional, typed against the same callGatewayFromCli contract from openclaw/plugin-sdk/browser-node-runtime, and documented inline. Production callers pass nothing and get the real runtime.

@greptile-apps

greptile-apps Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a silent "0 artifacts" bug in openclaw wiki bridge import by routing the CLI command through the wiki.bridge.import gateway RPC instead of calling syncMemoryWikiImportedSources in-process, where memory-core's capability is not registered. The fix is well-scoped, the addGatewayClientOptions wiring matches the pattern used by other CLI commands, and the new tests correctly lock in the routing contract.

  • The required config: ResolvedMemoryWikiConfig parameter in runWikiBridgeImport is now unused by the implementation; making it optional would be non-breaking and less misleading to future callers (see inline comment).

Confidence Score: 5/5

Safe to merge — the fix is a clean routing change with appropriate tests and no protocol or persistence changes.

All findings are P2 style suggestions. The core logic (RPC routing, opts forwarding, error handling) is correct and well-tested. No security, data-integrity, or reliability concerns were identified.

No files require special attention beyond the minor config parameter cleanup noted in extensions/memory-wiki/src/cli.ts.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/memory-wiki/src/cli.ts
Line: 510-520

Comment:
**`config` is now a required-but-unused parameter**

`config: ResolvedMemoryWikiConfig` is declared as required in the new implementation, but the function body never reads `params.config` (nor `params.appConfig`). Callers — including external plugin consumers of this exported function — are forced to construct and supply a full `ResolvedMemoryWikiConfig` that the gateway RPC path silently discards. Making `config` optional (`config?: ResolvedMemoryWikiConfig`) is non-breaking for every existing caller (they already pass it) and avoids the misleading API surface.

```suggestion
export async function runWikiBridgeImport(params: {
  config?: ResolvedMemoryWikiConfig;
  appConfig?: OpenClawConfig;
  gatewayOpts?: GatewayRpcOpts;
  json?: boolean;
  stdout?: Pick<NodeJS.WriteStream, "write">;
  // Injectable for tests; production code routes through the gateway RPC so the
  // command sees the memory-core capability that is only registered inside the
  // gateway daemon process (issue #67190).
  callGateway?: typeof callGatewayFromCli;
}) {
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(memory-wiki): route wiki bridge impo..." | Re-trigger Greptile

Comment on lines 510 to 520
export async function runWikiBridgeImport(params: {
config: ResolvedMemoryWikiConfig;
appConfig?: OpenClawConfig;
gatewayOpts?: GatewayRpcOpts;
json?: boolean;
stdout?: Pick<NodeJS.WriteStream, "write">;
// Injectable for tests; production code routes through the gateway RPC so the
// command sees the memory-core capability that is only registered inside the
// gateway daemon process (issue #67190).
callGateway?: typeof callGatewayFromCli;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 config is now a required-but-unused parameter

config: ResolvedMemoryWikiConfig is declared as required in the new implementation, but the function body never reads params.config (nor params.appConfig). Callers — including external plugin consumers of this exported function — are forced to construct and supply a full ResolvedMemoryWikiConfig that the gateway RPC path silently discards. Making config optional (config?: ResolvedMemoryWikiConfig) is non-breaking for every existing caller (they already pass it) and avoids the misleading API surface.

Suggested change
export async function runWikiBridgeImport(params: {
config: ResolvedMemoryWikiConfig;
appConfig?: OpenClawConfig;
gatewayOpts?: GatewayRpcOpts;
json?: boolean;
stdout?: Pick<NodeJS.WriteStream, "write">;
// Injectable for tests; production code routes through the gateway RPC so the
// command sees the memory-core capability that is only registered inside the
// gateway daemon process (issue #67190).
callGateway?: typeof callGatewayFromCli;
}) {
export async function runWikiBridgeImport(params: {
config?: ResolvedMemoryWikiConfig;
appConfig?: OpenClawConfig;
gatewayOpts?: GatewayRpcOpts;
json?: boolean;
stdout?: Pick<NodeJS.WriteStream, "write">;
// Injectable for tests; production code routes through the gateway RPC so the
// command sees the memory-core capability that is only registered inside the
// gateway daemon process (issue #67190).
callGateway?: typeof callGatewayFromCli;
}) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/memory-wiki/src/cli.ts
Line: 510-520

Comment:
**`config` is now a required-but-unused parameter**

`config: ResolvedMemoryWikiConfig` is declared as required in the new implementation, but the function body never reads `params.config` (nor `params.appConfig`). Callers — including external plugin consumers of this exported function — are forced to construct and supply a full `ResolvedMemoryWikiConfig` that the gateway RPC path silently discards. Making `config` optional (`config?: ResolvedMemoryWikiConfig`) is non-breaking for every existing caller (they already pass it) and avoids the misleading API surface.

```suggestion
export async function runWikiBridgeImport(params: {
  config?: ResolvedMemoryWikiConfig;
  appConfig?: OpenClawConfig;
  gatewayOpts?: GatewayRpcOpts;
  json?: boolean;
  stdout?: Pick<NodeJS.WriteStream, "write">;
  // Injectable for tests; production code routes through the gateway RPC so the
  // command sees the memory-core capability that is only registered inside the
  // gateway daemon process (issue #67190).
  callGateway?: typeof callGatewayFromCli;
}) {
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied — made config optional since the gateway RPC path doesn't use it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 111f4dff50

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +932 to +935
delete entry.MediaPath;
delete entry.MediaPaths;
delete entry.MediaType;
delete entry.MediaTypes;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve non-image media metadata during image promotion

When a user message contains mixed media types, this branch promotes only image/* paths into content blocks but then deletes MediaPath, MediaPaths, MediaType, and MediaTypes unconditionally. That drops audio/video/document attachment metadata from chat.history responses whenever at least one image is present, which is a regression for any client flow that still reads non-image attachments from the legacy media fields. Keep non-image entries (or only remove the promoted image entries) instead of clearing all media fields.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want me to implement the fix and push?

@zwj0117

zwj0117 commented Apr 19, 2026

Copy link
Copy Markdown

Can confirm this bug on v2026.4.15. Running openclaw wiki bridge import gives 0 artifacts even though the gateway RPC returns the same result. Switching to unsafe-local mode with explicit paths is the current workaround that actually works.

The `openclaw wiki bridge import` CLI command ran in a fresh subprocess
where memory-core's `registerMemoryCapability` is never called, so
`listActiveMemoryPublicArtifacts` always returned [] and the command
reported 0 artifacts even when the gateway RPC `wiki.bridge.import`
found real artifacts. Route the CLI through `callGatewayFromCli` so
both paths see the same capability registered inside the gateway daemon.

Adds `--url` / `--token` / `--timeout` / `--expect-final` to the
`wiki bridge import` subcommand via `addGatewayClientOptions`, and
surfaces a clear error when the gateway is unreachable instead of the
prior silent zero-artifact false negative.

Closes openclaw#67190
Server transcript stores user images as MediaPath/MediaPaths fields
rather than inline image content blocks. After chat.history reload
replaced optimistic local messages, extractImages found nothing to
render. Promote image MediaPaths into content blocks server-side and
resolve local file paths through the assistant-media endpoint client-side.

Closes openclaw#67487
@moorsecopers99
moorsecopers99 force-pushed the fix/memory-wiki-cli-bridge-import branch from 111f4df to 31228f2 Compare April 20, 2026 00:56
@moorsecopers99

Copy link
Copy Markdown
Author

Hi @zwj0117, thanks for the confirmation on v2026.4.15. Fix is in this PR: runWikiBridgeImport was calling syncMemoryWikiImportedSources in-process, but that helper depends on memoryPluginState.capability which only gets registered inside the gateway daemon. In a fresh CLI subprocess the capability is undefined, so listActiveMemoryPublicArtifacts short-circuits to [] and you get 0 artifacts — which is also exactly why the unsafe-local path worked for you (different code path that doesn't touch that capability). The PR now routes wiki bridge import through the wiki.bridge.import gateway RPC via callGatewayFromCli, so the CLI reports the same count as openclaw gateway call wiki.bridge.import. Just rebased onto latest main and the conflicts are resolved — should be mergeable now.

#67208

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31228f2d49

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +538 to +539
const mime = (mediaTypes[i] ?? "").toLowerCase();
if (!mime.startsWith("image/")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align media types with paths before promoting images

The promotion loop assumes MediaTypes[i] always describes MediaPaths[i], but that invariant is not guaranteed across the repo (for example buildMediaPayload can drop empty MIME entries, producing shorter MediaTypes arrays). In that case this code can mark the wrong path as an image (or skip a real image), and because sanitizeChatHistoryMessage deletes legacy media fields after any promotion, chat history reloads can permanently show the wrong attachment set for that message.

Useful? React with 👍 / 👎.

@altierac

Copy link
Copy Markdown

This PR fixes Root Cause #1 (CLI loader gap) from our investigation. Confirmed that the CLI path loads cli-metadata.ts which never calls registerMemoryCapability(), while the gateway path loads the full index.ts.

However, there's a second root cause that this PR alone won't fix: loader.ts snapshot restore omits the capability field (see #66082 comment for details). This means even the gateway RPC path can intermittently return 0 artifacts after plugin reload events.

Both fixes are needed for reliable bridge import. See detailed investigation in #66082.

@altierac

Copy link
Copy Markdown

Detailed investigation report covering both root causes and why this PR alone is a partial fix: https://altiera.jp/research/bridge-zero-artifacts.md

@moorsecopers99

Copy link
Copy Markdown
Author

Thanks for the detailed root-cause split. Confirmed the second issue against src/plugins/loader.ts on main: the per-plugin register loop captures previousAgentHarnesses, previousMemoryPromptBuilder, previousMemoryRuntime, etc., but never captures getMemoryCapabilityRegistration(). Both rollback sites (snapshot path at !shouldActivate and the register-failure catch) then call restoreMemoryPluginState({ ... }) without a capability field, and restoreMemoryPluginState explicitly sets memoryPluginState.capability = undefined when the field is missing — so any snapshot-scoped load or failed register() wipes an already-registered capability.

Pushing a follow-up that captures previousMemoryCapability alongside the other previous-state snapshots and passes it back in both restore calls, plus a loader.test.ts regression that registers an active capability, runs an activate: false load of an unrelated plugin, and asserts the capability + public artifacts survive. Will link the PR here once it's up.

@altierac altierac left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@vincentkoc

Copy link
Copy Markdown
Member

Thanks for pushing the Gateway-routing direction here. I opened the scoped maintainer fix at #71479 and credited this PR there.

I'm closing this one because it mixes the Memory Wiki CLI bridge fix with unrelated chat-history media-path changes, which makes it riskier to land as-is. The replacement PR keeps the same root-cause path: active bridge-mode CLI commands now call the Gateway so they see the runtime memory plugin artifacts instead of the empty standalone CLI registry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dedupe:child Duplicate issue/PR child in dedupe cluster

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: memory-wiki: openclaw wiki bridge import CLI returns 0 artifacts while openclaw gateway call wiki.bridge.import works correctly

4 participants