Skip to content

feat(plugins): support multi-kind plugins for dual slot ownership#57507

Merged
obviyus merged 7 commits into
openclaw:mainfrom
fuller-stack-dev:feat/multi-kind-plugins
Mar 31, 2026
Merged

feat(plugins): support multi-kind plugins for dual slot ownership#57507
obviyus merged 7 commits into
openclaw:mainfrom
fuller-stack-dev:feat/multi-kind-plugins

Conversation

@fuller-stack-dev

Copy link
Copy Markdown
Member

Summary

  • Allow plugins to declare kind as an array (e.g. ["memory", "context-engine"]) so a single plugin can occupy multiple exclusive slots simultaneously.
  • Add normalizeKinds(), hasKind(), and slotKeysForPluginKind() helpers in slots.ts for uniform multi-kind iteration and comparison.
  • Update manifest parsing, slot selection, loader, registry, config validation, and all record.kind === "memory" comparisons to use the new helpers.

Motivation

A plugin that acts as both a memory provider and a context engine currently can only declare one kind and occupy one slot. This change lets such a plugin declare kind: ["memory", "context-engine"] to occupy both slots, disabling competing plugins in each slot as expected.

Changes

  • src/plugins/slots.ts -- Added normalizeKinds, hasKind, slotKeysForPluginKind. Updated slotKeyForPluginKind and applyExclusiveSlotSelection to accept PluginKind | PluginKind[] and iterate over all slot keys.
  • src/plugins/types.ts -- OpenClawPluginDefinition.kind now accepts PluginKind | PluginKind[].
  • src/plugins/manifest.ts -- Added parsePluginKind() to handle both string and array kind in manifests.
  • src/plugins/registry.ts -- PluginRecord.kind accepts PluginKind | PluginKind[]; memory-gated registrations use hasKind().
  • src/plugins/manifest-registry.ts -- Updated PluginManifestRecord.kind type.
  • src/plugins/loader.ts -- All record.kind === "memory" checks replaced with hasKind(record.kind, "memory"). Kind mismatch comparison updated for array support.
  • src/plugins/config-state.ts -- resolveMemorySlotDecision accepts string | string[] kind param.
  • src/hooks/plugin-hooks.ts, src/agents/skills/plugin-skills.ts, src/config/validation.ts -- Updated memory kind checks to use hasKind().

Test plan

  • Added tests for normalizeKinds, hasKind, slotKeysForPluginKind in slots.test.ts
  • Added test for multi-kind slot selection (both slots updated, competing plugins disabled)
  • All existing slots.test.ts tests pass (15 total)
  • All loader.test.ts tests pass (73 total)
  • All config-state.test.ts tests pass (25 total)
  • pnpm lint passes with 0 errors

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M labels Mar 30, 2026
@greptile-apps

greptile-apps Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds multi-kind plugin support so a single plugin can declare kind: [\"memory\", \"context-engine\"] and occupy both exclusive slots simultaneously, disabling competing plugins in each. The implementation is solid: normalizeKinds, hasKind, and slotKeysForPluginKind are clean helpers, applyExclusiveSlotSelection correctly accumulates state across all slot iterations, and all record.kind === \"memory\" comparisons in the codebase are consistently updated to use hasKind. Previous review concerns (order-sensitive JSON.stringify comparison and hardcoded slot reverse-mapping) are confirmed fixed in this revision.

Two minor points remain:

  • slotKeyForPluginKind (singular) was widened to accept PluginKind | PluginKind[] but still only returns the first kind's slot key. It has no callers in the codebase, but its new signature could mislead future callers — slotKeysForPluginKind (plural) is the correct function for multi-kind use.
  • parsePluginKind accepts an empty array kind: [] and returns [] rather than undefined, silently treating it as "no kind" with no validation feedback to the plugin author.

Confidence Score: 5/5

Safe to merge — all prior P1 concerns are resolved and only minor P2 style issues remain.

All previous review thread concerns (order-sensitive JSON.stringify comparison, hardcoded slot reverse-mapping, inline hasKind duplication) are confirmed fixed. The core multi-kind slot logic is correct: state accumulation in applyExclusiveSlotSelection is sound, competitor disabling works correctly for both single- and dual-kind plugins, and all equality checks across the codebase consistently use hasKind. The two remaining findings are P2 style/edge-case concerns that do not affect correctness.

src/plugins/slots.ts (slotKeyForPluginKind singular API), src/plugins/manifest.ts (empty-array edge case in parsePluginKind)

Important Files Changed

Filename Overview
src/plugins/slots.ts Core multi-kind slot logic. Helpers are correctly implemented. Minor concern: slotKeyForPluginKind (singular, exported) accepts multi-kind arrays but silently returns only the first kind's slot key.
src/plugins/manifest.ts New parsePluginKind helper correctly handles string and array forms. Minor edge case: kind: [] passes through as [] rather than undefined.
src/plugins/loader.ts All kind === "memory" comparisons replaced with hasKind. Kind mismatch diagnostic uses normalizeKinds().sort() for order-insensitive comparison in both load paths.
src/plugins/registry.ts All four memory-gated registration guards updated to use hasKind. Type widened correctly.
src/plugins/config-state.ts resolveMemorySlotDecision now accepts string
src/plugins/slots.test.ts New tests cover normalizeKinds, hasKind, slotKeysForPluginKind, and the multi-kind dual-slot selection scenario.
src/plugins/types.ts OpenClawPluginDefinition.kind widened to PluginKind
src/plugins/manifest-registry.ts PluginManifestRecord.kind type widened to PluginKind
src/agents/skills/plugin-skills.ts record.kind === "memory" replaced with hasKind(record.kind, "memory"). Correct one-line update.
src/hooks/plugin-hooks.ts record.kind === "memory" replaced with hasKind(record.kind, "memory"). Correct one-line update.
src/config/validation.ts record.kind === "memory" replaced with hasKind(record.kind, "memory"). Correct one-line update.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/plugins/slots.ts
Line: 41-47

Comment:
**`slotKeyForPluginKind` silently truncates multi-kind arrays**

The function signature was widened to accept `PluginKind | PluginKind[]`, but the implementation only ever returns the slot key for the *first* element of an array. A future caller passing `["memory", "context-engine"]` would silently receive only `"memory"` with no indication that the second kind was ignored.

`slotKeysForPluginKind` (plural) is the correct API for multi-kind plugins and is the function used everywhere else in this PR. Since `slotKeyForPluginKind` is exported (public API) and currently has no callers in the codebase, consider either:

1. Narrowing its signature back to `PluginKind` only (makes the single-result contract explicit), or
2. Adding a JSDoc comment that makes the "first kind wins" behaviour clear.

```suggestion
/**
 * Returns the slot key for a single-kind plugin.
 * For multi-kind plugins use `slotKeysForPluginKind` instead.
 */
export function slotKeyForPluginKind(kind?: PluginKind): PluginSlotKey | null {
  if (!kind) {
    return null;
  }
  return SLOT_BY_KIND[kind] ?? null;
}
```

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

---

This is a comment left during a code review.
Path: src/plugins/manifest.ts
Line: 236-244

Comment:
**Empty array `kind: []` silently passes through as a valid (empty) kind**

`[].every(...)` is vacuously `true`, so a manifest with `kind: []` returns `[]` rather than `undefined`. Downstream code handles the empty array correctly (e.g. `normalizeKinds([])``[]`, `hasKind([], ...)``false`), but a plugin author who accidentally writes `kind: []` gets no validation feedback and the plugin is treated as if it had no kind at all.

Consider rejecting empty arrays explicitly:

```suggestion
function parsePluginKind(raw: unknown): PluginKind | PluginKind[] | undefined {
  if (typeof raw === "string") {
    return raw as PluginKind;
  }
  if (Array.isArray(raw) && raw.length > 0 && raw.every((k) => typeof k === "string")) {
    return raw.length === 1 ? (raw[0] as PluginKind) : (raw as PluginKind[]);
  }
  return undefined;
}
```

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

Reviews (2): Last reviewed commit: "fix: address review feedback on multi-ki..." | Re-trigger Greptile

Comment thread src/plugins/loader.ts Outdated
Comment thread src/plugins/loader.ts Outdated
Comment thread src/plugins/slots.ts Outdated
Comment thread src/plugins/config-state.ts Outdated
Comment thread .github/workflows/sync-fork.yml Outdated

@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: 377a6794fa

ℹ️ 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 thread src/plugins/loader.ts Outdated
@fuller-stack-dev
fuller-stack-dev force-pushed the feat/multi-kind-plugins branch from 377a679 to 6de0e43 Compare March 30, 2026 05:55
@fuller-stack-dev

Copy link
Copy Markdown
Member Author

@greptileai another review please

@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: a4bd2fee6a

ℹ️ 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 thread src/plugins/slots.ts
@xDarkicex

Copy link
Copy Markdown
Contributor

This is the exact thing that my plugin needs to function correctly

@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: 0992aae02c

ℹ️ 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 thread src/plugins/config-state.ts

@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: 60ff18cfaf

ℹ️ 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 thread src/plugins/loader.ts
@miyago9267

Copy link
Copy Markdown

nice approach — the helpers clean up the scattered kind === "memory" checks really well. couple minor things:

the kind mismatch comparison in loader.ts (JSON.stringify([...normalizeKinds(x)].sort())) appears twice — might be worth extracting to a kindsEqual() helper in slots.ts to keep it DRY and avoid the stringify overhead

slotKeyForPluginKind silently returns only the first kind's slot — could trip up future callers. a jsdoc note like "returns the first matching slot; use slotKeysForPluginKind for all slots" would help

overall looks solid, the dual-kind "stays enabled when it loses one slot" behavior makes sense and the tests cover it well

@fuller-stack-dev

Copy link
Copy Markdown
Member Author

@miyago9267 Thanks for the review! Addressed both points in 9d8293f:

  1. Extracted kindsEqual() helper in slots.ts — both loader.ts comparison sites now use it instead of the JSON.stringify(normalizeKinds().sort()) pattern
  2. Narrowed slotKeyForPluginKind back to single PluginKind and added a JSDoc noting to use slotKeysForPluginKind for multi-kind plugins

@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: ed8bbaac31

ℹ️ 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 thread src/plugins/registry.ts
Comment thread src/plugins/slots.ts Outdated
@obviyus obviyus self-assigned this Mar 31, 2026
- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic
When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.
A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.
- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests
@obviyus
obviyus force-pushed the feat/multi-kind-plugins branch from e2e5c9f to 4718725 Compare March 31, 2026 04:35

@obviyus obviyus 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.

Reviewed latest changes; landing now.

@obviyus
obviyus merged commit 235908c into openclaw:main Mar 31, 2026
19 of 20 checks passed
@obviyus

obviyus commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Landed on main.

Thanks @fuller-stack-dev.

@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: 4718725303

ℹ️ 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".

}
// A dual-kind plugin (e.g. ["memory", "context-engine"]) that lost the
// memory slot must stay enabled so its other slot role can still load.
const isMultiKind = Array.isArray(params.kind) && params.kind.length > 1;

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 Determine dual-kind status from unique mapped kinds

resolveMemorySlotDecision uses Array.isArray(params.kind) && params.kind.length > 1 to decide whether a plugin should be exempt from memory-slot disablement, but manifest parsing accepts any non-empty string array. That means inputs like kind: ["memory", "memory"] (or "memory" plus a typo) are treated as dual-kind and remain enabled when plugins.slots.memory is set to another plugin or none, so the plugin still runs register() side effects despite not owning a valid second slot. The dual-kind check should use distinct, recognized slot-mapped kinds rather than raw array length.

Useful? React with 👍 / 👎.

pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…) (thanks @fuller-stack-dev)

* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…) (thanks @fuller-stack-dev)

* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…) (thanks @fuller-stack-dev)

* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…) (thanks @fuller-stack-dev)

* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…) (thanks @fuller-stack-dev)

* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…) (thanks @fuller-stack-dev)

* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
Nachx639 pushed a commit to Nachx639/clawdbot that referenced this pull request Jun 17, 2026
…) (thanks @fuller-stack-dev)

* feat(plugins): support multi-kind plugins for dual slot ownership

* fix: address review feedback on multi-kind plugin support

- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
  (fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
  (removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic

* fix: don't disable dual-kind plugin that still owns another slot

When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.

* fix: exempt dual-kind plugins from memory slot disablement

A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.

* fix: address remaining review feedback

- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests

* fix: use toSorted() instead of sort() per lint rules

* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants