Skip to content

Fix #1559: fix: registerMemoryCapability API not found in OpenClaw 2026.3.31#2037

Merged
syzsunshine219 merged 6 commits into
MemTensor:dev-v2.0.22from
Memtensor-AI:bugfix/autodev-1559
Jul 2, 2026
Merged

Fix #1559: fix: registerMemoryCapability API not found in OpenClaw 2026.3.31#2037
syzsunshine219 merged 6 commits into
MemTensor:dev-v2.0.22from
Memtensor-AI:bugfix/autodev-1559

Conversation

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

Description

Fixes #1559: the @memtensor/memos-local-openclaw-plugin failed to load on OpenClaw 2026.3.31 because it still called the removed umbrella api.registerMemoryCapability({promptBuilder}) at apps/memos-local-openclaw/index.ts:163. OpenClaw 2026.3.31 split that umbrella into three focused registrars — registerMemoryPromptSection, registerMemoryFlushPlan, registerMemoryRuntime — so the call threw TypeError: api.registerMemoryCapability is not a function and the plugin refused to initialize.

The fix rewrites register() to feature-detect the new registerMemoryPromptSection(builder) API and use it preferentially, falling back to the legacy registerMemoryCapability({ promptBuilder }) for older gateways, and logging a warning if neither method is present. Only the prompt-section builder is needed — the plugin does not implement flush plans or memory runtimes — so this is a minimal, backwards-compatible migration.

Test evidence: a new tests/memory-registration-api.test.ts (3 cases) locks in preference of the new API, the legacy-fallback path, and the no-throw safeguard. Six existing test stubs were extended to expose both entry points. npx vitest run tests/memory-registration-api.test.ts → 3/3 pass; targeted run of touched files (plugin-openclaw-wiring, plugin-impl-access, shutdown-lifecycle, hub-eager-connect, integration, incremental-sharing) → 41/41 pass. Broader npx vitest run --exclude e2e/accuracy → 213 pass; the 3 pre-existing failures (skill-auto-install, task-processor, update-install) exist on the base branch too and are unrelated to this change (confirmed via git stash re-run). npx tsc --noEmit is clean.

Related Issue (Required): Fixes #1559

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (does not change functionality, e.g. code style improvements, linting)
  • Documentation update

How Has This Been Tested?

Automated tests are pending.

  • Unit Test
  • Test Script Or Test Steps (please provide)
  • Pipeline Automated API Test (please provide)

Checklist

  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • I have created related documentation issue/PR in MemOS-Docs (if applicable)
  • I have linked the issue to this PR (if applicable)
  • I have mentioned the person who will review this PR

@MatthewZhuang, @CarltonXiang, @syzsunshine219, @World-controller please review this PR.

Reviewer Checklist

OpenClaw 2026.3.31 replaced the umbrella `registerMemoryCapability`
with three focused registrars (`registerMemoryPromptSection`,
`registerMemoryFlushPlan`, `registerMemoryRuntime`), so the plugin's
call at index.ts:163 failed with `TypeError: api.registerMemoryCapability
is not a function` and the whole plugin refused to load.

The register() entry now feature-detects `registerMemoryPromptSection`
and falls back to the legacy `registerMemoryCapability` for older
gateways, warning if neither exists. Tests stub both entry points and a
new tests/memory-registration-api.test.ts locks in the preference order,
the fallback path, and the no-throw safeguard.

Fixes MemTensor#1559
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

🤖 Open Code Review

Target: PR #2037
Task: aa7384b576a80c35
Base: dev-20260624-v2.0.22
Head: bugfix/autodev-1559

🔍 OpenCodeReview found 4 issue(s) in this PR.


1. apps/memos-local-openclaw/index.ts (L167-L170)

Silent failure risk on old gateways: The intermediate else if branch calls memoryApi.registerMemoryCapability(...) through the narrowed cast type where registerMemoryCapability is typed as optional (?: ...). TypeScript will accept this, but if the runtime gateway object somehow has registerMemoryCapability present on the prototype chain rather than as an own property, typeof checks will still pass — which is intended. This is fine.

However, the more significant concern is: the memoryApi cast strips the type down to only the two optional methods, discarding every other member of OpenClawPluginApi. This means that if you ever need to fall through to the else branch and call api.logger.warn(...), you are correctly falling back to the original api reference — good. But this also means the two paths are tested against different objects at the type level, which can be a subtle maintenance trap if someone later accesses other API members through memoryApi instead of api.

Consider extracting the feature-detection into a small helper or narrowing to a more descriptive named interface to make the intent explicit and guard future misuse:

interface MemoryRegistrationApi {
  registerMemoryPromptSection?: (builder: typeof buildMemoryPromptSection) => void;
  registerMemoryCapability?: (opts: { promptBuilder: typeof buildMemoryPromptSection }) => void;
}
const memoryApi = api as unknown as MemoryRegistrationApi;

This is already done inline, so the only real suggestion is to hoist the interface to a named declaration at the module or function scope for discoverability.


2. apps/memos-local-openclaw/index.ts (L176-L178)

Missing error-level escalation at startup: The else branch only emits a warn log, but the consequence is severe — the entire memory prompt injection is silently skipped, so the AI will never recall memories. On an unrecognised gateway version this should arguably throw an error or at the very least use api.logger.error(...) so operators notice immediately rather than debugging broken memory recall later.

Suggested change:

api.logger.error(
  "memos-local: neither api.registerMemoryPromptSection nor api.registerMemoryCapability is available; memory prompt section will not be injected. Plugin may be incompatible with this OpenClaw gateway version.",
);
// Optionally: throw new Error("memos-local: no compatible memory registration API found on OpenClawPluginApi");

3. apps/memos-local-openclaw/index.ts (L167-L170)

Inline anonymous cast type reduces maintainability: The anonymous object type in as unknown as { ... } works correctly, but hoisting it to a named interface at module/function scope would improve discoverability and prevent future maintainers from accidentally accessing other OpenClawPluginApi members through memoryApi (where only these two methods are typed). Consider:

interface MemoryRegistrationApi {
  registerMemoryPromptSection?: (builder: typeof buildMemoryPromptSection) => void;
  registerMemoryCapability?: (capability: { promptBuilder: typeof buildMemoryPromptSection }) => void;
}
const memoryApi = api as unknown as MemoryRegistrationApi;

4. apps/memos-local-openclaw/index.ts (L176-L178)

Severity of the fallthrough is understated: When neither registration method is found, the entire memory prompt injection is silently skipped — the AI will never surface recalled memories for the user. A warn log is easy to miss in production. Consider using api.logger.error(...) or even throwing to make the misconfiguration immediately visible to operators, rather than manifesting as subtle broken recall behavior:

api.logger.error(
  "memos-local: neither api.registerMemoryPromptSection nor api.registerMemoryCapability is available; memory prompt section will not be injected. The plugin may be incompatible with this OpenClaw gateway version.",
);
// Optionally: throw new Error("memos-local: no compatible memory registration API found");

Generated by cloud-assistant via Open Code Review.

…+ escalate missing-registrar log to error

Address OpenClaw review feedback on the MemTensor#1559 fix:

* Hoist the inline `{ registerMemoryPromptSection?, registerMemoryCapability? }`
  cast type to a named `MemoryRegistrationApi` interface at module scope so
  future maintainers see which host methods the feature detection covers and
  don't accidentally reach for other `OpenClawPluginApi` members through the
  narrowed reference. (OCR MemTensor#1, MemTensor#3)

* Escalate the "neither registrar available" log from `warn` to `error` so the
  fatal misconfiguration surfaces immediately in production dashboards rather
  than silently manifesting as broken memory recall. Falls back to `warn` when
  the host logger lacks `.error` for compatibility with legacy `HostLogger`
  shapes. (OCR MemTensor#2, MemTensor#4)

* Expand `tests/memory-registration-api.test.ts` accordingly: assert `error`
  is called on the modern path and add a legacy-logger case that verifies the
  warn fallback still fires when `.error` is absent.
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

🤖 Open Code Review

Target: PR #2037
Task: aa7384b576a80c35
Base: dev-20260624-v2.0.22
Head: bugfix/autodev-1559

🔍 OpenCodeReview found 3 issue(s) in this PR.


1. apps/memos-local-openclaw/index.ts (L183)

Unsafe double cast (as unknown as MemoryRegistrationApi) discards type safety without a compensating guard.

The cast api as unknown as MemoryRegistrationApi completely bypasses the TypeScript type checker for the api object. Because OpenClawPluginApi is already imported as an explicit type (line 8), it would be safer — and more idiomatic — to extend or intersect it with the optional fields instead of escaping through unknown. Consider:

type MemoryRegistrationAwareApi = OpenClawPluginApi & MemoryRegistrationApi;
const memoryApi = api as MemoryRegistrationAwareApi;

This still compiles (both sides share a common base), preserves the rest of api's typed members on memoryApi, and makes the intent much clearer: "this API might have these extra fields".

💡 Suggested Change

Before:

    const memoryApi = api as unknown as MemoryRegistrationApi;

After:

    const memoryApi = api as OpenClawPluginApi & MemoryRegistrationApi;

2. apps/memos-local-openclaw/index.ts (L193-L202)

Silent failure — the else branch only logs but does not throw or signal the error to the caller.

When neither registrar is present, the method continues executing (the rest of register() runs normally, setting up stores, workers, tools, etc.) even though the memory recall path is permanently broken for this session. This is particularly risky because all the setup work still succeeds — operators may see a healthy startup log with a single error line buried in the middle, and the plugin will appear to be running.

Consider throwing an error (or at minimum returning early) so the host can handle the incompatibility explicitly:

const err = new Error(message);
const errorLog = (api.logger as { error?: (msg: string) => void }).error;
if (typeof errorLog === "function") {
  errorLog.call(api.logger, message);
} else {
  api.logger.warn(message);
}
throw err; // prevent the rest of register() from running in a broken state

3. apps/memos-local-openclaw/index.ts (L197)

Repeated as { error?: ... } cast on api.logger duplicates the same unsafe narrowing pattern.

Just like the memoryApi cast above, (api.logger as { error?: (msg: string) => void }) introduces another inline escape hatch. If OpenClawPluginApi already defines logger with a typed interface (e.g., it has warn but not error), this cast silently diverges from the canonical definition. Consider defining a small local type for the extended logger alongside MemoryRegistrationApi at module scope, to keep all type-escaping in one place and make it easy to remove once the SDK is updated:

interface ExtendedLogger {
  warn(msg: string): void;
  error?(msg: string): void;
}
const logger = api.logger as ExtendedLogger;
const errorLog = logger.error;

Generated by cloud-assistant via Open Code Review.

…mory registrar

Address the second round of OpenClaw review feedback on the MemTensor#1559 fix:

* Replace `api as unknown as MemoryRegistrationApi` with the safer
  intersection cast `api as OpenClawPluginApi & MemoryRegistrationApi`.
  This keeps every other typed member of `api` intact and communicates
  the actual intent — "the host may additionally expose these optional
  registrars" — instead of throwing away the whole type. (OCR MemTensor#1)

* Throw after logging in the "no registrar found" branch. Previously
  register() would keep spinning up stores, workers and tools even
  though the recall path was permanently broken, producing a plugin
  that *looks* healthy while silently never surfacing memories. Now
  the failure is visible at startup and the host can react. (OCR MemTensor#2)

* Hoist the inline `{ error?: (msg: string) => void }` cast on
  `api.logger` into a named `ExtendedLogger` interface alongside
  `MemoryRegistrationApi`, so all remaining type-escape hatches live
  in one place and can be dropped once the SDK publishes matching
  types. (OCR MemTensor#3)

* Update `tests/memory-registration-api.test.ts` accordingly: the
  "neither method" cases now assert both the error/warn log and that
  `register()` throws with a matching message, so the fail-fast
  contract is locked in.
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

🤖 Open Code Review

Target: PR #2037
Task: aa7384b576a80c35
Base: dev-20260624-v2.0.22
Head: bugfix/autodev-1559

OpenCodeReview: No comments generated. Looks good to me.

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI Memtensor-AI changed the base branch from dev-20260624-v2.0.22 to dev-v2.0.22 July 2, 2026 07:28
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

❌ Automated Test Results: FAILED

Auto-fix retry 1/2 triggered.

Failed tests:

  • npm test
Error details
Tests failed. Failed cases: npm test

Branch: bugfix/autodev-1559

@CarltonXiang CarltonXiang added the plugin Plugin/adapter/bridge layer (apps/ directory) | 插件/适配层 label Jul 2, 2026
MemOS AutoDev and others added 2 commits July 2, 2026 15:54
…ayMs in update-install test

The mock `res.end(payload)` in the update-install test ignored the optional
flush callback, so `jsonResponseAndRestart` never scheduled its 1500ms
SIGUSR1 setTimeout under `vi.useFakeTimers()`. Also bump the timer advance
from 500ms to 2000ms to cover the actual default `delayMs=1500`.

Fixes: "keeps the new version and restarts only after a successful postinstall"
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

Automated Test Results: PASSED\n\nCloud test-engine rerun after resolving the dev-v2.0.22 merge conflict.\n\nRun: tr-4b3ba62f-a76\nScope: memos_local_openclaw\nResult: 58/58 tests passed\nCommand group: memos_local_openclaw/unit\nDuration: 66s\n\nLocal pre-push verification also passed: npm run build, plus focused vitest for the memory-registration API tests.\n\nStatus: merge conflict resolved; automated scope test passed. Manual code review is still required before merge.

@syzsunshine219 syzsunshine219 merged commit c9f270f into MemTensor:dev-v2.0.22 Jul 2, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated bug Something isn't working | 功能异常 plugin Plugin/adapter/bridge layer (apps/ directory) | 插件/适配层

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants