From c59af3caf72ef509f5205c660a7d264c14b43bea Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 12:17:06 +0100 Subject: [PATCH] docs(plugins): document runtime config APIs --- AGENTS.md | 1 - CHANGELOG.md | 1 + docs/plugins/sdk-migration.md | 37 ++++++++++++++++++++++++++++++++++ docs/plugins/sdk-runtime.md | 38 ++++++++++++++++++++++++++++++++--- docs/plugins/sdk-testing.md | 5 +++-- 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3369f409cac..6bc2c27b4f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,7 +55,6 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work. - Formatting: use `oxfmt`, not Prettier. Prefer `pnpm format:check` / `pnpm format`; for targeted files use `pnpm exec oxfmt --check --threads=1 ` or `pnpm exec oxfmt --write --threads=1 `. - Linting: use repo wrappers (`pnpm lint:*`, `scripts/run-oxlint.mjs`); do not invoke generic JS formatters/lints unless a repo script uses them. - Heavy checks: `OPENCLAW_LOCAL_CHECK=1`, mode `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; CI/shared use `OPENCLAW_LOCAL_CHECK=0`. -- Local first. Use repo `pnpm` lanes before Blacksmith/Testbox. Remote only for parity-only failures, secrets/services, or explicit ask. - Blacksmith/Testbox is maintainer opt-in, not a repo-wide default. If Blacksmith access is available and `OPENCLAW_TESTBOX=1` is set, or a maintainer's personal AGENTS rules ask for it, use Testbox for broad, slow, Docker, live, E2E, full-suite, or CI-parity validation instead of running those heavy lanes locally. Use `OPENCLAW_LOCAL_CHECK_MODE=throttled|full` as the explicit local escape hatch. - Testbox use: run from repo root, pre-warm early with `blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90`, reuse the returned `tbx_...` id for all `run`/`download` commands, and stop boxes you created before handoff. Timeout bins: `90` minutes default, `240` multi-hour, `720` all-day, `1440` overnight; anything above `1440` needs explicit approval and cleanup. - Testbox full-suite profile: `blacksmith testbox run --id "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test"`. For installable package proof, prefer the GitHub `Package Acceptance` workflow over ad hoc Testbox commands. diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d7369f55a..f06bd2838d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai - Memory/OpenAI-compatible: add optional `memorySearch.inputType`, `queryInputType`, and `documentInputType` config for asymmetric embedding endpoints, including direct query embeddings and provider batch indexing. Carries forward #63313 and #60727. Thanks @HOYALIM and @prospect1314521. - Ollama/memory: add model-specific retrieval query prefixes for `nomic-embed-text`, `qwen3-embedding`, and `mxbai-embed-large` memory-search queries while leaving document batches unchanged. Carries forward #45013. Thanks @laolin5564. - Plugins/providers: move pre-runtime model-id normalization, provider endpoint host metadata, and OpenAI-compatible request-family hints into plugin manifests so core no longer carries bundled-provider routing tables. Thanks @codex. +- Plugins/config: deprecate direct plugin config load/write helpers in favor of passed runtime snapshots plus transactional mutation helpers with explicit restart follow-up policy. Thanks @codex. - Plugins/install: allow `OPENCLAW_PLUGIN_STAGE_DIR` to contain layered runtime-dependency roots, resolving read-only preinstalled deps before installing missing deps into the final writable root. Fixes #72396. Thanks @liorb-mountapps. - Control UI: add a raw config pending-changes diff panel that parses JSON5, redacts sensitive values until reveal, and avoids fake raw-edit callbacks when opening the panel. Refs #39831; supersedes #48621 and #46654. Thanks @JiajunBernoulli and @BunsDev. - Control UI: polish the quick settings dashboard grid so common cards align across desktop, tablet, and mobile layouts without wasting horizontal space. Thanks @BunsDev. diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 9e5f5d6001e..91e2a64b819 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -90,6 +90,43 @@ releases. ## How to migrate + + Bundled plugins should stop calling + `api.runtime.config.loadConfig()` and + `api.runtime.config.writeConfigFile(...)` directly. Prefer config that was + already passed into the active call path. Long-lived handlers that need the + current process snapshot can use `api.runtime.config.current()`. Long-lived + agent tools should use the tool context's `ctx.getRuntimeConfig()` inside + `execute` so a tool created before a config write still sees the refreshed + runtime config. + + Config writes must go through the transactional helpers and choose an + after-write policy: + + ```typescript + await api.runtime.config.mutateConfigFile({ + afterWrite: { mode: "auto" }, + mutate(draft) { + draft.plugins ??= {}; + }, + }); + ``` + + Use `afterWrite: { mode: "restart", reason: "..." }` when the caller knows + the change requires a clean gateway restart, and + `afterWrite: { mode: "none", reason: "..." }` only when the caller owns the + follow-up and deliberately wants to suppress the reload planner. + Mutation results include a typed `followUp` summary for tests and logging; + the gateway remains responsible for applying or scheduling the restart. + `loadConfig` and `writeConfigFile` remain as deprecated compatibility + helpers for external plugins during the migration window. Bundled plugins + and repo runtime code are protected by scanner guardrails: new production + plugin usage fails outright, direct config writes fail, gateway server + methods must use the request runtime snapshot, and long-lived runtime + modules have zero allowed ambient `loadConfig()` calls. + + + Bundled plugins must replace Pi-only `api.registerEmbeddedExtensionFactory(...)` tool-result handlers with diff --git a/docs/plugins/sdk-runtime.md b/docs/plugins/sdk-runtime.md index be423cf8e87..5fea7ef5718 100644 --- a/docs/plugins/sdk-runtime.md +++ b/docs/plugins/sdk-runtime.md @@ -25,6 +25,26 @@ register(api) { } ``` +## Config Loading And Writes + +Prefer config that was already passed into the active call path, for example `api.config` during registration or a `cfg` argument on channel/provider callbacks. This keeps one process snapshot flowing through the work instead of reparsing config on hot paths. + +Use `api.runtime.config.current()` only when a long-lived handler needs the current process snapshot and no config was passed to that function. The returned value is readonly; clone or use a mutation helper before editing. + +Tool factories receive `ctx.runtimeConfig` plus `ctx.getRuntimeConfig()`. Use the getter inside a long-lived tool's `execute` callback when config can change after the tool definition was created. + +Persist changes with `api.runtime.config.mutateConfigFile(...)` or `api.runtime.config.replaceConfigFile(...)`. Each write must choose an explicit `afterWrite` policy: + +- `afterWrite: { mode: "auto" }` lets the gateway reload planner decide. +- `afterWrite: { mode: "restart", reason: "..." }` forces a clean restart when the writer knows hot reload is unsafe. +- `afterWrite: { mode: "none", reason: "..." }` suppresses automatic reload/restart only when the caller owns the follow-up. + +The mutation helpers return `afterWrite` plus a typed `followUp` summary so callers can log or test whether they requested a restart. The gateway still owns when that restart actually happens. + +`api.runtime.config.loadConfig()` and `api.runtime.config.writeConfigFile(...)` are deprecated compatibility helpers. Bundled plugins must not use them; the architecture guard fails if production plugin code calls them or imports those helpers from plugin SDK subpaths. + +Internal OpenClaw runtime code has the same direction: load config once at the CLI, gateway, or process boundary, then pass that value through. Long-lived runtime modules have a zero-tolerance scanner for ambient `loadConfig()` calls; use a passed `cfg`, a request `context.getRuntimeConfig()`, or `getRuntimeConfig()` at an explicit process boundary. + ## Runtime namespaces @@ -284,13 +304,25 @@ register(api) { - Config load and write. + Current runtime config snapshot and transactional config writes. Prefer + config that was already passed into the active call path; use + `current()` only when the handler needs the process snapshot directly. ```typescript - const cfg = await api.runtime.config.loadConfig(); - await api.runtime.config.writeConfigFile(cfg); + const cfg = api.runtime.config.current(); + await api.runtime.config.mutateConfigFile({ + afterWrite: { mode: "auto" }, + mutate(draft) { + draft.plugins ??= {}; + }, + }); ``` + `mutateConfigFile(...)` and `replaceConfigFile(...)` return a `followUp` + value, for example `{ mode: "restart", requiresRestart: true, reason }`, + which records the writer intent without taking restart control away from the + gateway. + System-level utilities. diff --git a/docs/plugins/sdk-testing.md b/docs/plugins/sdk-testing.md index aeff3bcf898..bdcf258bf6b 100644 --- a/docs/plugins/sdk-testing.md +++ b/docs/plugins/sdk-testing.md @@ -178,8 +178,9 @@ const mockRuntime = { // ... other mocks }, config: { - loadConfig: vi.fn(), - writeConfigFile: vi.fn(), + current: vi.fn(() => ({}) as const), + mutateConfigFile: vi.fn(), + replaceConfigFile: vi.fn(), }, // ... other namespaces } as unknown as PluginRuntime;