From d772e34368ebeaf007536b489fa6b47dda4b5109 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Tue, 21 Jul 2026 16:50:33 -0700 Subject: [PATCH] fix(openclaw): declare gbrain plugin manifest entry (takeover of #2551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the OpenClaw-required top-level id to openclaw.plugin.json, export a direct register(api) entrypoint from src/openclaw-context-engine.ts, add a manifest regression test, and document that skillpack harvest must preserve OpenClaw-native manifest fields (id, configSchema, contracts). llms bundles regenerated (bun run build:llms) — no content drift. Co-authored-by: Filip Co-Authored-By: Claude Fable 5 --- docs/guides/skillpacks-as-scaffolding.md | 4 ++- openclaw.plugin.json | 1 + skills/skillpack-harvest/SKILL.md | 3 ++- skills/testing/SKILL.md | 4 ++- src/openclaw-context-engine.ts | 31 ++++++++++++------------ test/openclaw-plugin-manifest.test.ts | 17 +++++++++++++ 6 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 test/openclaw-plugin-manifest.test.ts diff --git a/docs/guides/skillpacks-as-scaffolding.md b/docs/guides/skillpacks-as-scaffolding.md index b58d8e511..db33a8a1d 100644 --- a/docs/guides/skillpacks-as-scaffolding.md +++ b/docs/guides/skillpacks-as-scaffolding.md @@ -131,7 +131,9 @@ into gbrain so other clients can scaffold it. Default behavior: `~/.gbrain/harvest-private-patterns.txt` plus built-in defaults (canonical private fork name, common email regex, Slack channel pattern). Any match → rollback (delete the harvested files) and exit non-zero. -- `openclaw.plugin.json` updated with the new slug, sorted. +- `openclaw.plugin.json` updated with the new slug, sorted. Harvest must preserve + the top-level OpenClaw-native plugin fields (`id`, `configSchema`, `contracts`) + because OpenClaw validates those before it can install the package. - `--no-lint` bypasses the linter (after a manual editorial scrub). Use the `skillpack-harvest` skill (its companion editorial workflow) diff --git a/openclaw.plugin.json b/openclaw.plugin.json index e6c1b0533..f12bb6445 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,4 +1,5 @@ { + "id": "gbrain-context-engine", "name": "gbrain", "version": "0.32.3.0", "description": "Personal knowledge brain with Postgres + pgvector hybrid search", diff --git a/skills/skillpack-harvest/SKILL.md b/skills/skillpack-harvest/SKILL.md index a19e217b5..bd6f2dfbb 100644 --- a/skills/skillpack-harvest/SKILL.md +++ b/skills/skillpack-harvest/SKILL.md @@ -266,4 +266,5 @@ editorial pass. (e.g. `src/commands/.ts` if the host SKILL.md declares it in frontmatter) - gbrain's `openclaw.plugin.json` — adds the slug to `skills:` - array, sorted alphabetically + array, sorted alphabetically, without removing OpenClaw-native plugin fields + like `id`, `configSchema`, or `contracts` diff --git a/skills/testing/SKILL.md b/skills/testing/SKILL.md index 790820c56..9d73437d1 100644 --- a/skills/testing/SKILL.md +++ b/skills/testing/SKILL.md @@ -57,6 +57,8 @@ This mode guarantees: - `skills/manifest.json` lists every skill directory - `skills/RESOLVER.md` references every skill in the manifest - `openclaw.plugin.json` `skills[]` round-trips with both +- `openclaw.plugin.json` keeps OpenClaw install-required native plugin fields + (`id`, object `configSchema`, and `contracts.contextEngines` when applicable) - No MECE violations (duplicate triggers across skills) ### Phases @@ -72,7 +74,7 @@ This mode guarantees: ### Automation ```bash -bun test test/skills-conformance.test.ts test/resolver.test.ts +bun test test/skills-conformance.test.ts test/resolver.test.ts test/openclaw-plugin-manifest.test.ts ``` The CI-gated check is the package.json `test` script. diff --git a/src/openclaw-context-engine.ts b/src/openclaw-context-engine.ts index b1809523f..a1055cf29 100644 --- a/src/openclaw-context-engine.ts +++ b/src/openclaw-context-engine.ts @@ -63,25 +63,26 @@ interface PluginCtx { [key: string]: unknown; } +export function register(api: PluginApi) { + api.registerContextEngine(ENGINE_ID, (ctx: PluginCtx) => { + const hostResolver = + typeof ctx.resolveEntities === 'function' + ? ctx.resolveEntities + : typeof ctx.brainQuery === 'function' + ? ctx.brainQuery + : undefined; + return createGBrainContextEngine({ + workspaceDir: ctx.workspaceDir, + resolveEntities: hostResolver, + }); + }); +} + const entry: PluginEntry = { id: 'gbrain-context-engine', name: 'GBrain Context Engine', description: 'Deterministic temporal/spatial context injection on every turn', - - register(api: PluginApi) { - api.registerContextEngine(ENGINE_ID, (ctx: PluginCtx) => { - const hostResolver = - typeof ctx.resolveEntities === 'function' - ? ctx.resolveEntities - : typeof ctx.brainQuery === 'function' - ? ctx.brainQuery - : undefined; - return createGBrainContextEngine({ - workspaceDir: ctx.workspaceDir, - resolveEntities: hostResolver, - }); - }); - }, + register, }; export default entry; diff --git a/test/openclaw-plugin-manifest.test.ts b/test/openclaw-plugin-manifest.test.ts new file mode 100644 index 000000000..f06882f8d --- /dev/null +++ b/test/openclaw-plugin-manifest.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'bun:test'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +describe('root OpenClaw plugin manifest', () => { + it('declares the id required by OpenClaw plugin installs', () => { + const manifest = JSON.parse(readFileSync(join(import.meta.dir, '..', 'openclaw.plugin.json'), 'utf8')); + const entrySource = readFileSync(join(import.meta.dir, '..', 'src', 'openclaw-context-engine.ts'), 'utf8'); + const entryId = entrySource.match(/id:\s*'([^']+)'/)?.[1]; + + expect(manifest.id).toBe(entryId); + expect(manifest.configSchema).toBeDefined(); + expect(typeof manifest.configSchema).toBe('object'); + expect(manifest.contracts?.contextEngines).toContain('gbrain-context'); + expect(entrySource).toContain('export function register'); + }); +});