From 03855539183742b7991029c7e95bc1835204473a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 17 Mar 2026 23:48:00 -0700 Subject: [PATCH 1/6] Plugin SDK: trim lobster and qwen helper exports --- extensions/lobster/runtime-api.ts | 2 +- extensions/lobster/src/lobster-tool.test.ts | 2 +- extensions/qwen-portal-auth/runtime-api.ts | 2 +- package.json | 8 -------- scripts/lib/plugin-sdk-entrypoints.json | 2 -- src/plugin-sdk/subpaths.test.ts | 2 ++ src/plugins/contracts/runtime.contract.test.ts | 4 ++-- 7 files changed, 7 insertions(+), 15 deletions(-) diff --git a/extensions/lobster/runtime-api.ts b/extensions/lobster/runtime-api.ts index 7ab2351b77d..24898e04cf5 100644 --- a/extensions/lobster/runtime-api.ts +++ b/extensions/lobster/runtime-api.ts @@ -1 +1 @@ -export * from "openclaw/plugin-sdk/lobster"; +export * from "../../src/plugin-sdk/lobster.js"; diff --git a/extensions/lobster/src/lobster-tool.test.ts b/extensions/lobster/src/lobster-tool.test.ts index 62c0fed6d81..8c010e20f11 100644 --- a/extensions/lobster/src/lobster-tool.test.ts +++ b/extensions/lobster/src/lobster-tool.test.ts @@ -3,7 +3,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { PassThrough } from "node:stream"; -import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk/lobster"; +import type { OpenClawPluginApi, OpenClawPluginToolContext } from "../runtime-api.js"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { createWindowsCmdShimFixture, diff --git a/extensions/qwen-portal-auth/runtime-api.ts b/extensions/qwen-portal-auth/runtime-api.ts index 232a2886110..ccd9abae569 100644 --- a/extensions/qwen-portal-auth/runtime-api.ts +++ b/extensions/qwen-portal-auth/runtime-api.ts @@ -1 +1 @@ -export * from "openclaw/plugin-sdk/qwen-portal-auth"; +export * from "../../src/plugin-sdk/qwen-portal-auth.js"; diff --git a/package.json b/package.json index c4cdd342df1..2a17025c18a 100644 --- a/package.json +++ b/package.json @@ -242,10 +242,6 @@ "types": "./dist/plugin-sdk/irc.d.ts", "default": "./dist/plugin-sdk/irc.js" }, - "./plugin-sdk/lobster": { - "types": "./dist/plugin-sdk/lobster.d.ts", - "default": "./dist/plugin-sdk/lobster.js" - }, "./plugin-sdk/lazy-runtime": { "types": "./dist/plugin-sdk/lazy-runtime.d.ts", "default": "./dist/plugin-sdk/lazy-runtime.js" @@ -274,10 +270,6 @@ "types": "./dist/plugin-sdk/nostr.d.ts", "default": "./dist/plugin-sdk/nostr.js" }, - "./plugin-sdk/qwen-portal-auth": { - "types": "./dist/plugin-sdk/qwen-portal-auth.d.ts", - "default": "./dist/plugin-sdk/qwen-portal-auth.js" - }, "./plugin-sdk/synology-chat": { "types": "./dist/plugin-sdk/synology-chat.d.ts", "default": "./dist/plugin-sdk/synology-chat.js" diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index ba136b70f6d..cce8dfe895a 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -50,7 +50,6 @@ "feishu", "googlechat", "irc", - "lobster", "lazy-runtime", "matrix", "mattermost", @@ -58,7 +57,6 @@ "minimax-portal-auth", "nextcloud-talk", "nostr", - "qwen-portal-auth", "synology-chat", "testing", "test-utils", diff --git a/src/plugin-sdk/subpaths.test.ts b/src/plugin-sdk/subpaths.test.ts index 6a5cec3d57c..d7b9399a0f2 100644 --- a/src/plugin-sdk/subpaths.test.ts +++ b/src/plugin-sdk/subpaths.test.ts @@ -48,9 +48,11 @@ const trimmedLegacyExtensionSubpaths = [ "diagnostics-otel", "diffs", "llm-task", + "lobster", "memory-lancedb", "open-prose", "phone-control", + "qwen-portal-auth", "talk-voice", "thread-ownership", "voice-call", diff --git a/src/plugins/contracts/runtime.contract.test.ts b/src/plugins/contracts/runtime.contract.test.ts index f3985500af4..ba6e7df1187 100644 --- a/src/plugins/contracts/runtime.contract.test.ts +++ b/src/plugins/contracts/runtime.contract.test.ts @@ -18,8 +18,8 @@ vi.mock("@mariozechner/pi-ai/oauth", async () => { }; }); -vi.mock("openclaw/plugin-sdk/qwen-portal-auth", async () => { - const actual = await vi.importActual("openclaw/plugin-sdk/qwen-portal-auth"); +vi.mock("../../plugin-sdk/qwen-portal-auth.js", async () => { + const actual = await vi.importActual("../../plugin-sdk/qwen-portal-auth.js"); return { ...actual, refreshQwenPortalCredentials: refreshQwenPortalCredentialsMock, From 5eea523f39f5204a1e7c245e41282841f81d5815 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 17 Mar 2026 23:52:32 -0700 Subject: [PATCH 2/6] UI: remove dead control UI modules --- pnpm-lock.yaml | 18 +---- ui/package.json | 8 +- ui/src/styles/layout.mobile.css | 73 +---------------- ui/src/ui/chat-export.ts | 1 - ui/src/ui/data/moonshot-kimi-k2.ts | 45 ----------- ui/src/ui/tool-labels.ts | 39 --------- ui/src/ui/views/bottom-tabs.ts | 33 -------- ui/src/ui/views/config-search.node.test.ts | 50 ------------ ui/src/ui/views/config-search.ts | 92 ---------------------- ui/src/ui/views/overview-quick-actions.ts | 31 -------- 10 files changed, 6 insertions(+), 384 deletions(-) delete mode 100644 ui/src/ui/chat-export.ts delete mode 100644 ui/src/ui/data/moonshot-kimi-k2.ts delete mode 100644 ui/src/ui/tool-labels.ts delete mode 100644 ui/src/ui/views/bottom-tabs.ts delete mode 100644 ui/src/ui/views/config-search.node.test.ts delete mode 100644 ui/src/ui/views/config-search.ts delete mode 100644 ui/src/ui/views/overview-quick-actions.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b43381e461c..4fb25b899d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -625,12 +625,6 @@ importers: ui: dependencies: - '@lit-labs/signals': - specifier: ^0.2.0 - version: 0.2.0 - '@lit/context': - specifier: ^1.1.6 - version: 1.1.6 '@noble/ed25519': specifier: 3.0.1 version: 3.0.1 @@ -643,15 +637,6 @@ importers: marked: specifier: ^17.0.4 version: 17.0.4 - signal-polyfill: - specifier: ^0.2.2 - version: 0.2.2 - signal-utils: - specifier: ^0.21.1 - version: 0.21.1(signal-polyfill@0.2.2) - vite: - specifier: 8.0.0 - version: 8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) devDependencies: '@vitest/browser-playwright': specifier: 4.1.0 @@ -662,6 +647,9 @@ importers: playwright: specifier: ^1.58.2 version: 1.58.2 + vite: + specifier: 8.0.0 + version: 8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: 4.1.0 version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(jsdom@29.0.0(@noble/hashes@2.0.1))(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) diff --git a/ui/package.json b/ui/package.json index 71eb17fe80a..5d514f671cd 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,20 +9,16 @@ "test": "vitest run --config vitest.config.ts" }, "dependencies": { - "@lit-labs/signals": "^0.2.0", - "@lit/context": "^1.1.6", "@noble/ed25519": "3.0.1", "dompurify": "^3.3.3", "lit": "^3.3.2", - "marked": "^17.0.4", - "signal-polyfill": "^0.2.2", - "signal-utils": "^0.21.1", - "vite": "8.0.0" + "marked": "^17.0.4" }, "devDependencies": { "@vitest/browser-playwright": "4.1.0", "jsdom": "^29.0.0", "playwright": "^1.58.2", + "vite": "8.0.0", "vitest": "4.1.0" } } diff --git a/ui/src/styles/layout.mobile.css b/ui/src/styles/layout.mobile.css index e459bca2bca..6d943253804 100644 --- a/ui/src/styles/layout.mobile.css +++ b/ui/src/styles/layout.mobile.css @@ -249,6 +249,7 @@ .topnav-shell__content { display: none; + width: 100%; } .topbar-nav-toggle { @@ -650,75 +651,3 @@ font-size: 12px; } } - -/* =========================================== - Bottom Tabs (mobile navigation bar) - =========================================== */ - -.bottom-tabs { - display: none; -} - -@media (max-width: 768px) { - .bottom-tabs { - display: flex; - position: fixed; - bottom: 0; - left: 0; - right: 0; - z-index: 60; - background: var(--bg); - border-top: 1px solid var(--border); - padding: 4px 0 calc(4px + env(safe-area-inset-bottom, 0px)); - justify-content: space-around; - align-items: stretch; - } - - .bottom-tab { - display: flex; - flex-direction: column; - align-items: center; - gap: 2px; - flex: 1; - padding: 6px 4px; - border: none; - background: none; - color: var(--muted); - font-size: 10px; - cursor: pointer; - transition: - color var(--duration-fast) ease, - opacity var(--duration-fast) ease; - } - - .bottom-tab__icon { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - } - - .bottom-tab__icon svg { - width: 20px; - height: 20px; - stroke: currentColor; - fill: none; - stroke-width: 1.5px; - stroke-linecap: round; - stroke-linejoin: round; - } - - .bottom-tab__label { - font-weight: 500; - letter-spacing: 0.01em; - } - - .bottom-tab--active { - color: var(--accent); - } - - .bottom-tab:active { - opacity: 0.7; - } -} diff --git a/ui/src/ui/chat-export.ts b/ui/src/ui/chat-export.ts deleted file mode 100644 index ed5bbf931f8..00000000000 --- a/ui/src/ui/chat-export.ts +++ /dev/null @@ -1 +0,0 @@ -export { exportChatMarkdown } from "./chat/export.ts"; diff --git a/ui/src/ui/data/moonshot-kimi-k2.ts b/ui/src/ui/data/moonshot-kimi-k2.ts deleted file mode 100644 index f9aa8d1311e..00000000000 --- a/ui/src/ui/data/moonshot-kimi-k2.ts +++ /dev/null @@ -1,45 +0,0 @@ -export const MOONSHOT_KIMI_K2_DEFAULT_ID = "kimi-k2.5"; -export const MOONSHOT_KIMI_K2_CONTEXT_WINDOW = 256000; -export const MOONSHOT_KIMI_K2_MAX_TOKENS = 8192; -export const MOONSHOT_KIMI_K2_INPUT = ["text"] as const; -export const MOONSHOT_KIMI_K2_COST = { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, -} as const; - -export const MOONSHOT_KIMI_K2_MODELS = [ - { - id: "kimi-k2.5", - name: "Kimi K2.5", - alias: "Kimi K2.5", - reasoning: false, - }, - { - id: "kimi-k2-0905-preview", - name: "Kimi K2 0905 Preview", - alias: "Kimi K2", - reasoning: false, - }, - { - id: "kimi-k2-turbo-preview", - name: "Kimi K2 Turbo", - alias: "Kimi K2 Turbo", - reasoning: false, - }, - { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - alias: "Kimi K2 Thinking", - reasoning: true, - }, - { - id: "kimi-k2-thinking-turbo", - name: "Kimi K2 Thinking Turbo", - alias: "Kimi K2 Thinking Turbo", - reasoning: true, - }, -] as const; - -export type MoonshotKimiK2Model = (typeof MOONSHOT_KIMI_K2_MODELS)[number]; diff --git a/ui/src/ui/tool-labels.ts b/ui/src/ui/tool-labels.ts deleted file mode 100644 index e4818c49362..00000000000 --- a/ui/src/ui/tool-labels.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Map raw tool names to human-friendly labels for the chat UI. - * Unknown tools are title-cased with underscores replaced by spaces. - */ - -export const TOOL_LABELS: Record = { - exec: "Run Command", - bash: "Run Command", - read: "Read File", - write: "Write File", - edit: "Edit File", - apply_patch: "Apply Patch", - web_search: "Web Search", - web_fetch: "Fetch Page", - browser: "Browser", - message: "Send Message", - image: "Generate Image", - canvas: "Canvas", - cron: "Cron", - gateway: "Gateway", - nodes: "Nodes", - memory_search: "Search Memory", - memory_get: "Get Memory", - session_status: "Session Status", - sessions_list: "List Sessions", - sessions_history: "Session History", - sessions_send: "Send to Session", - sessions_spawn: "Spawn Session", - agents_list: "List Agents", -}; - -export function friendlyToolName(raw: string): string { - const mapped = TOOL_LABELS[raw]; - if (mapped) { - return mapped; - } - // Title-case fallback: "some_tool_name" → "Some Tool Name" - return raw.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); -} diff --git a/ui/src/ui/views/bottom-tabs.ts b/ui/src/ui/views/bottom-tabs.ts deleted file mode 100644 index b8dfbebf39c..00000000000 --- a/ui/src/ui/views/bottom-tabs.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { html } from "lit"; -import { icons } from "../icons.ts"; -import type { Tab } from "../navigation.ts"; - -export type BottomTabsProps = { - activeTab: Tab; - onTabChange: (tab: Tab) => void; -}; - -const BOTTOM_TABS: Array<{ id: Tab; label: string; icon: keyof typeof icons }> = [ - { id: "overview", label: "Dashboard", icon: "barChart" }, - { id: "chat", label: "Chat", icon: "messageSquare" }, - { id: "sessions", label: "Sessions", icon: "fileText" }, - { id: "config", label: "Settings", icon: "settings" }, -]; - -export function renderBottomTabs(props: BottomTabsProps) { - return html` - - `; -} diff --git a/ui/src/ui/views/config-search.node.test.ts b/ui/src/ui/views/config-search.node.test.ts deleted file mode 100644 index d1a5a09d837..00000000000 --- a/ui/src/ui/views/config-search.node.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - appendTagFilter, - getTagFilters, - hasTagFilter, - removeTagFilter, - replaceTagFilters, - toggleTagFilter, -} from "./config-search.ts"; - -describe("config search tag helper", () => { - it("adds a tag when query is empty", () => { - expect(appendTagFilter("", "security")).toBe("tag:security"); - }); - - it("appends a tag to existing text query", () => { - expect(appendTagFilter("token", "security")).toBe("token tag:security"); - }); - - it("deduplicates existing tag filters case-insensitively", () => { - expect(appendTagFilter("token tag:Security", "security")).toBe("token tag:Security"); - }); - - it("detects exact tag terms", () => { - expect(hasTagFilter("tag:security token", "security")).toBe(true); - expect(hasTagFilter("tag:security-hard token", "security")).toBe(false); - }); - - it("removes only the selected active tag", () => { - expect(removeTagFilter("token tag:security tag:auth", "security")).toBe("token tag:auth"); - }); - - it("toggle removes active tag and keeps text", () => { - expect(toggleTagFilter("token tag:security", "security")).toBe("token"); - }); - - it("toggle adds missing tag", () => { - expect(toggleTagFilter("token", "channels")).toBe("token tag:channels"); - }); - - it("extracts unique normalized tags from query", () => { - expect(getTagFilters("token tag:Security tag:auth tag:security")).toEqual(["security", "auth"]); - }); - - it("replaces only tag filters and preserves free text", () => { - expect(replaceTagFilters("token tag:security mode", ["auth", "channels"])).toBe( - "token mode tag:auth tag:channels", - ); - }); -}); diff --git a/ui/src/ui/views/config-search.ts b/ui/src/ui/views/config-search.ts deleted file mode 100644 index f6973d3a2cd..00000000000 --- a/ui/src/ui/views/config-search.ts +++ /dev/null @@ -1,92 +0,0 @@ -function escapeRegExp(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - -function normalizeTag(tag: string): string { - return tag.trim().toLowerCase(); -} - -export function getTagFilters(query: string): string[] { - const seen = new Set(); - const tags: string[] = []; - const pattern = /(^|\s)tag:([^\s]+)/gi; - const raw = query.trim(); - let match: RegExpExecArray | null = pattern.exec(raw); - while (match) { - const normalized = normalizeTag(match[2] ?? ""); - if (normalized && !seen.has(normalized)) { - seen.add(normalized); - tags.push(normalized); - } - match = pattern.exec(raw); - } - return tags; -} - -export function hasTagFilter(query: string, tag: string): boolean { - const normalizedTag = normalizeTag(tag); - if (!normalizedTag) { - return false; - } - const pattern = new RegExp(`(^|\\s)tag:${escapeRegExp(normalizedTag)}(?=\\s|$)`, "i"); - return pattern.test(query.trim()); -} - -export function appendTagFilter(query: string, tag: string): string { - const normalizedTag = normalizeTag(tag); - const trimmed = query.trim(); - if (!normalizedTag) { - return trimmed; - } - if (!trimmed) { - return `tag:${normalizedTag}`; - } - if (hasTagFilter(trimmed, normalizedTag)) { - return trimmed; - } - return `${trimmed} tag:${normalizedTag}`; -} - -export function removeTagFilter(query: string, tag: string): string { - const normalizedTag = normalizeTag(tag); - const trimmed = query.trim(); - if (!normalizedTag || !trimmed) { - return trimmed; - } - const pattern = new RegExp(`(^|\\s)tag:${escapeRegExp(normalizedTag)}(?=\\s|$)`, "ig"); - return trimmed.replace(pattern, " ").replace(/\s+/g, " ").trim(); -} - -export function replaceTagFilters(query: string, tags: readonly string[]): string { - const uniqueTags: string[] = []; - const seen = new Set(); - for (const tag of tags) { - const normalized = normalizeTag(tag); - if (!normalized || seen.has(normalized)) { - continue; - } - seen.add(normalized); - uniqueTags.push(normalized); - } - - const trimmed = query.trim(); - const withoutTags = trimmed - .replace(/(^|\s)tag:([^\s]+)/gi, " ") - .replace(/\s+/g, " ") - .trim(); - const tagTokens = uniqueTags.map((tag) => `tag:${tag}`).join(" "); - if (withoutTags && tagTokens) { - return `${withoutTags} ${tagTokens}`; - } - if (withoutTags) { - return withoutTags; - } - return tagTokens; -} - -export function toggleTagFilter(query: string, tag: string): string { - if (hasTagFilter(query, tag)) { - return removeTagFilter(query, tag); - } - return appendTagFilter(query, tag); -} diff --git a/ui/src/ui/views/overview-quick-actions.ts b/ui/src/ui/views/overview-quick-actions.ts deleted file mode 100644 index b1358ca2e67..00000000000 --- a/ui/src/ui/views/overview-quick-actions.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { html } from "lit"; -import { t } from "../../i18n/index.ts"; -import { icons } from "../icons.ts"; - -export type OverviewQuickActionsProps = { - onNavigate: (tab: string) => void; - onRefresh: () => void; -}; - -export function renderOverviewQuickActions(props: OverviewQuickActionsProps) { - return html` -
- - - - -
- `; -} From bd444435c914e72e94d2ee5c010b1e30d7dc05ec Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 17 Mar 2026 23:53:30 -0700 Subject: [PATCH 3/6] Plugin SDK: clarify ACPX public seam --- src/plugin-sdk/acpx.ts | 4 ++-- src/plugin-sdk/subpaths.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugin-sdk/acpx.ts b/src/plugin-sdk/acpx.ts index 36da2f48810..9d634ec8fb5 100644 --- a/src/plugin-sdk/acpx.ts +++ b/src/plugin-sdk/acpx.ts @@ -1,5 +1,5 @@ -// Narrow plugin-sdk surface for the bundled acpx plugin. -// Keep this list additive and scoped to symbols used under extensions/acpx. +// Public ACPX runtime backend helpers. +// Keep this surface narrow and limited to the ACP runtime/backend contract. export type { AcpRuntimeErrorCode } from "../acp/runtime/errors.js"; export { AcpRuntimeError } from "../acp/runtime/errors.js"; diff --git a/src/plugin-sdk/subpaths.test.ts b/src/plugin-sdk/subpaths.test.ts index d7b9399a0f2..f3cd5537398 100644 --- a/src/plugin-sdk/subpaths.test.ts +++ b/src/plugin-sdk/subpaths.test.ts @@ -315,7 +315,7 @@ describe("plugin-sdk subpath exports", () => { expect(typeof tlonSdk.tlonSetupAdapter).toBe("object"); }); - it("exports acpx helpers", async () => { + it("exports ACPX runtime backend helpers", async () => { expect(typeof acpxSdk.listKnownProviderAuthEnvVarNames).toBe("function"); expect(typeof acpxSdk.omitEnvKeysCaseInsensitive).toBe("function"); }); From 8ac4b09fa429ddad71a85e8bb4f5c36dec0e28e3 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 17 Mar 2026 23:55:24 -0700 Subject: [PATCH 4/6] docs: fix em-dash headings and broken links across docs - Replace em-dashes in headings with hyphens/parens (breaks Mintlify anchors) - Fix broken /testing link in pi-dev.md to /help/testing - Convert absolute docs URLs to root-relative in pi-dev.md Files: migrating.md, images.md, audio.md, media-understanding.md, venice.md, minimax.md, AGENTS.default.md, security/index.md, pi-dev.md Co-Authored-By: Claude Opus 4.6 --- docs/gateway/security/index.md | 2 +- docs/install/migrating.md | 8 ++++---- docs/nodes/audio.md | 2 +- docs/nodes/images.md | 2 +- docs/nodes/media-understanding.md | 2 +- docs/pi-dev.md | 4 ++-- docs/providers/minimax.md | 2 +- docs/providers/venice.md | 4 ++-- docs/reference/AGENTS.default.md | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md index 5fbd26a826e..b9f37597b58 100644 --- a/docs/gateway/security/index.md +++ b/docs/gateway/security/index.md @@ -499,7 +499,7 @@ Treat the snippet above as **secure DM mode**: If you run multiple accounts on the same channel, use `per-account-channel-peer` instead. If the same person contacts you on multiple channels, use `session.identityLinks` to collapse those DM sessions into one canonical identity. See [Session Management](/concepts/session) and [Configuration](/gateway/configuration). -## Allowlists (DM + groups) — terminology +## Allowlists (DM + groups) - terminology OpenClaw has two separate “who can trigger me?” layers: diff --git a/docs/install/migrating.md b/docs/install/migrating.md index 7a925341abd..64c136be425 100644 --- a/docs/install/migrating.md +++ b/docs/install/migrating.md @@ -67,7 +67,7 @@ Those live under `$OPENCLAW_STATE_DIR`. ## Migration steps (recommended) -### Step 0 — Make a backup (old machine) +### Step 0 - Make a backup (old machine) On the **old** machine, stop the gateway first so files aren’t changing mid-copy: @@ -87,7 +87,7 @@ tar -czf openclaw-workspace.tgz .openclaw/workspace If you have multiple profiles/state dirs (e.g. `~/.openclaw-main`, `~/.openclaw-work`), archive each. -### Step 1 — Install OpenClaw on the new machine +### Step 1 - Install OpenClaw on the new machine On the **new** machine, install the CLI (and Node if needed): @@ -95,7 +95,7 @@ On the **new** machine, install the CLI (and Node if needed): At this stage, it’s OK if onboarding creates a fresh `~/.openclaw/` — you will overwrite it in the next step. -### Step 2 — Copy the state dir + workspace to the new machine +### Step 2 - Copy the state dir + workspace to the new machine Copy **both**: @@ -113,7 +113,7 @@ After copying, ensure: - Hidden directories were included (e.g. `.openclaw/`) - File ownership is correct for the user running the gateway -### Step 3 — Run Doctor (migrations + service repair) +### Step 3 - Run Doctor (migrations + service repair) On the **new** machine: diff --git a/docs/nodes/audio.md b/docs/nodes/audio.md index 1be35610323..57e9ab14d8a 100644 --- a/docs/nodes/audio.md +++ b/docs/nodes/audio.md @@ -5,7 +5,7 @@ read_when: title: "Audio and Voice Notes" --- -# Audio / Voice Notes — 2026-01-17 +# Audio / Voice Notes (2026-01-17) ## What works diff --git a/docs/nodes/images.md b/docs/nodes/images.md index c5f7bade748..6236ad089ef 100644 --- a/docs/nodes/images.md +++ b/docs/nodes/images.md @@ -5,7 +5,7 @@ read_when: title: "Image and Media Support" --- -# Image & Media Support — 2025-12-05 +# Image & Media Support (2025-12-05) The WhatsApp channel runs via **Baileys Web**. This document captures the current media handling rules for send, gateway, and agent replies. diff --git a/docs/nodes/media-understanding.md b/docs/nodes/media-understanding.md index ab3701387be..3178854ccfb 100644 --- a/docs/nodes/media-understanding.md +++ b/docs/nodes/media-understanding.md @@ -6,7 +6,7 @@ read_when: title: "Media Understanding" --- -# Media Understanding (Inbound) — 2026-01-17 +# Media Understanding - Inbound (2026-01-17) OpenClaw can **summarize inbound media** (image/audio/video) before the reply pipeline runs. It auto‑detects when local tools or provider keys are available, and can be disabled or customized. If understanding is off, models still receive the original files/URLs as usual. diff --git a/docs/pi-dev.md b/docs/pi-dev.md index 322bd13cd39..3b0918c4928 100644 --- a/docs/pi-dev.md +++ b/docs/pi-dev.md @@ -76,5 +76,5 @@ If you only want to reset sessions, delete `agents//sessions/` and `age ## References -- [https://docs.openclaw.ai/testing](https://docs.openclaw.ai/testing) -- [https://docs.openclaw.ai/start/getting-started](https://docs.openclaw.ai/start/getting-started) +- [Testing](/help/testing) +- [Getting Started](/start/getting-started) diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md index 0d3635352cc..c578a89d6e5 100644 --- a/docs/providers/minimax.md +++ b/docs/providers/minimax.md @@ -35,7 +35,7 @@ MiniMax highlights these improvements in M2.5: ## Choose a setup -### MiniMax OAuth (Coding Plan) — recommended +### MiniMax OAuth (Coding Plan) - recommended **Best for:** quick setup with MiniMax Coding Plan via OAuth, no API key required. diff --git a/docs/providers/venice.md b/docs/providers/venice.md index 520cf22d82b..6f3c4b9313d 100644 --- a/docs/providers/venice.md +++ b/docs/providers/venice.md @@ -124,7 +124,7 @@ openclaw models list | grep venice ## Available Models (41 Total) -### Private Models (26) — Fully Private, No Logging +### Private Models (26) - Fully Private, No Logging | Model ID | Name | Context | Features | | -------------------------------------- | ----------------------------------- | ------- | -------------------------- | @@ -155,7 +155,7 @@ openclaw models list | grep venice | `minimax-m21` | MiniMax M2.1 | 198k | Reasoning | | `minimax-m25` | MiniMax M2.5 | 198k | Reasoning | -### Anonymized Models (15) — Via Venice Proxy +### Anonymized Models (15) - Via Venice Proxy | Model ID | Name | Context | Features | | ------------------------------- | ------------------------------ | ------- | ------------------------- | diff --git a/docs/reference/AGENTS.default.md b/docs/reference/AGENTS.default.md index 7427f53c071..7bfb2351d0d 100644 --- a/docs/reference/AGENTS.default.md +++ b/docs/reference/AGENTS.default.md @@ -6,7 +6,7 @@ read_when: - Enabling or auditing default skills --- -# AGENTS.md — OpenClaw Personal Assistant (default) +# AGENTS.md - OpenClaw Personal Assistant (default) ## First run (recommended) From 3d31ba7830fd1c5e2cb8a869601bf5fe68739bf4 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 17 Mar 2026 23:56:49 -0700 Subject: [PATCH 5/6] Plugin SDK: guard package subpaths and fix Twitch setup export * fix(plugins): add missing secret-input-schema build entry and Matrix runtime export buildSecretInputSchema was not included in plugin-sdk-entrypoints.json, so it was never emitted to dist/plugin-sdk/secret-input-schema.js. This caused a ReferenceError during onboard when configuring channels that use secret input schemas (matrix, feishu, mattermost, bluebubbles, nextcloud-talk, zalo). Additionally, the Matrix extension's hand-written runtime-api barrel was missing the re-export, unlike other extensions that use `export *` from their plugin-sdk subpath. Co-Authored-By: Claude Opus 4.6 * Plugin SDK: guard package subpaths and fix Twitch setup export * Plugin SDK: fix import guardrail drift --------- Co-authored-by: hxy91819 Co-authored-by: Claude Opus 4.6 --- extensions/discord/src/directory-config.ts | 23 +-- extensions/slack/src/directory-config.ts | 23 ++- .../slack/src/message-action-dispatch.ts | 2 +- extensions/telegram/src/directory-config.ts | 23 +-- extensions/whatsapp/src/directory-config.ts | 2 +- extensions/whatsapp/src/normalize.ts | 2 + .../channel-import-guardrails.test.ts | 5 +- src/plugin-sdk/channel-runtime.ts | 1 + .../package-contract-guardrails.test.ts | 145 ++++++++++++++++++ src/plugin-sdk/runtime-api-guardrails.test.ts | 41 +++-- 10 files changed, 218 insertions(+), 49 deletions(-) create mode 100644 src/plugin-sdk/package-contract-guardrails.test.ts diff --git a/extensions/discord/src/directory-config.ts b/extensions/discord/src/directory-config.ts index 8828a1854eb..9c5e794924a 100644 --- a/extensions/discord/src/directory-config.ts +++ b/extensions/discord/src/directory-config.ts @@ -4,15 +4,20 @@ import { toDirectoryEntries, type DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; -import type { InspectedDiscordAccount } from "../../../src/channels/read-only-account-inspect.discord.runtime.js"; -import { inspectReadOnlyChannelAccount } from "../../../src/channels/read-only-account-inspect.js"; +import { inspectDiscordAccount } from "../api.js"; +import type { InspectedDiscordAccount } from "../api.js"; -export async function listDiscordDirectoryPeersFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "discord", +function inspectDiscordDirectoryAccount( + params: DirectoryConfigParams, +): InspectedDiscordAccount | null { + return inspectDiscordAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedDiscordAccount | null; + }); +} + +export async function listDiscordDirectoryPeersFromConfig(params: DirectoryConfigParams) { + const account = inspectDiscordDirectoryAccount(params); if (!account || !("config" in account)) { return []; } @@ -34,11 +39,7 @@ export async function listDiscordDirectoryPeersFromConfig(params: DirectoryConfi } export async function listDiscordDirectoryGroupsFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "discord", - cfg: params.cfg, - accountId: params.accountId, - })) as InspectedDiscordAccount | null; + const account = inspectDiscordDirectoryAccount(params); if (!account || !("config" in account)) { return []; } diff --git a/extensions/slack/src/directory-config.ts b/extensions/slack/src/directory-config.ts index 635222f9c2e..0bc0f49804e 100644 --- a/extensions/slack/src/directory-config.ts +++ b/extensions/slack/src/directory-config.ts @@ -1,3 +1,4 @@ +import { normalizeSlackMessagingTarget } from "openclaw/plugin-sdk/channel-runtime"; import { applyDirectoryQueryAndLimit, collectNormalizedDirectoryIds, @@ -5,16 +6,18 @@ import { toDirectoryEntries, type DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; -import { normalizeSlackMessagingTarget } from "../../../src/channels/plugins/normalize/slack.js"; -import { inspectReadOnlyChannelAccount } from "../../../src/channels/read-only-account-inspect.js"; -import type { InspectedSlackAccount } from "../../../src/channels/read-only-account-inspect.slack.runtime.js"; +import { inspectSlackAccount } from "../api.js"; +import type { InspectedSlackAccount } from "../api.js"; -export async function listSlackDirectoryPeersFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "slack", +function inspectSlackDirectoryAccount(params: DirectoryConfigParams): InspectedSlackAccount | null { + return inspectSlackAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedSlackAccount | null; + }); +} + +export async function listSlackDirectoryPeersFromConfig(params: DirectoryConfigParams) { + const account = inspectSlackDirectoryAccount(params); if (!account || !("config" in account)) { return []; } @@ -40,11 +43,7 @@ export async function listSlackDirectoryPeersFromConfig(params: DirectoryConfigP } export async function listSlackDirectoryGroupsFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "slack", - cfg: params.cfg, - accountId: params.accountId, - })) as InspectedSlackAccount | null; + const account = inspectSlackDirectoryAccount(params); if (!account || !("config" in account)) { return []; } diff --git a/extensions/slack/src/message-action-dispatch.ts b/extensions/slack/src/message-action-dispatch.ts index 4a2e17f5455..55576d9e822 100644 --- a/extensions/slack/src/message-action-dispatch.ts +++ b/extensions/slack/src/message-action-dispatch.ts @@ -1,7 +1,7 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core"; import type { ChannelMessageActionContext } from "openclaw/plugin-sdk/channel-runtime"; import { normalizeInteractiveReply } from "openclaw/plugin-sdk/interactive-runtime"; -import { readNumberParam, readStringParam } from "../../../src/agents/tools/common.js"; +import { readNumberParam, readStringParam } from "openclaw/plugin-sdk/slack-core"; import { parseSlackBlocksInput } from "./blocks-input.js"; import { buildSlackInteractiveBlocks } from "./blocks-render.js"; diff --git a/extensions/telegram/src/directory-config.ts b/extensions/telegram/src/directory-config.ts index 10abc88d784..3355b295cca 100644 --- a/extensions/telegram/src/directory-config.ts +++ b/extensions/telegram/src/directory-config.ts @@ -6,15 +6,20 @@ import { toDirectoryEntries, type DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; -import { inspectReadOnlyChannelAccount } from "../../../src/channels/read-only-account-inspect.js"; -import type { InspectedTelegramAccount } from "../../../src/channels/read-only-account-inspect.telegram.runtime.js"; +import { inspectTelegramAccount } from "../api.js"; +import type { InspectedTelegramAccount } from "../api.js"; -export async function listTelegramDirectoryPeersFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "telegram", +async function inspectTelegramDirectoryAccount( + params: DirectoryConfigParams, +): Promise { + return inspectTelegramAccount({ cfg: params.cfg, accountId: params.accountId, - })) as InspectedTelegramAccount | null; + }); +} + +export async function listTelegramDirectoryPeersFromConfig(params: DirectoryConfigParams) { + const account = await inspectTelegramDirectoryAccount(params); if (!account || !("config" in account)) { return []; } @@ -36,11 +41,7 @@ export async function listTelegramDirectoryPeersFromConfig(params: DirectoryConf } export async function listTelegramDirectoryGroupsFromConfig(params: DirectoryConfigParams) { - const account = (await inspectReadOnlyChannelAccount({ - channelId: "telegram", - cfg: params.cfg, - accountId: params.accountId, - })) as InspectedTelegramAccount | null; + const account = await inspectTelegramDirectoryAccount(params); if (!account || !("config" in account)) { return []; } diff --git a/extensions/whatsapp/src/directory-config.ts b/extensions/whatsapp/src/directory-config.ts index ad7b7d257e7..1a5fbbff9b0 100644 --- a/extensions/whatsapp/src/directory-config.ts +++ b/extensions/whatsapp/src/directory-config.ts @@ -3,8 +3,8 @@ import { listDirectoryUserEntriesFromAllowFrom, type DirectoryConfigParams, } from "openclaw/plugin-sdk/directory-runtime"; -import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../../src/whatsapp/normalize.js"; import { resolveWhatsAppAccount } from "./accounts.js"; +import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "./normalize.js"; export async function listWhatsAppDirectoryPeersFromConfig(params: DirectoryConfigParams) { const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId }); diff --git a/extensions/whatsapp/src/normalize.ts b/extensions/whatsapp/src/normalize.ts index bfecb31e4a5..d0506cd5883 100644 --- a/extensions/whatsapp/src/normalize.ts +++ b/extensions/whatsapp/src/normalize.ts @@ -1,5 +1,7 @@ export { + isWhatsAppGroupJid, looksLikeWhatsAppTargetId, normalizeWhatsAppAllowFromEntries, normalizeWhatsAppMessagingTarget, + normalizeWhatsAppTarget, } from "openclaw/plugin-sdk/channel-runtime"; diff --git a/src/plugin-sdk/channel-import-guardrails.test.ts b/src/plugin-sdk/channel-import-guardrails.test.ts index 69626948743..a4ca46a569c 100644 --- a/src/plugin-sdk/channel-import-guardrails.test.ts +++ b/src/plugin-sdk/channel-import-guardrails.test.ts @@ -252,7 +252,10 @@ function collectCoreSourceFiles(): string[] { fullPath.includes(".test.") || fullPath.includes(".spec.") || fullPath.includes(".fixture.") || - fullPath.includes(".snap") + fullPath.includes(".snap") || + // src/plugin-sdk is the curated bridge layer; validate its contracts with dedicated + // plugin-sdk guardrails instead of the generic "core should not touch extensions" rule. + fullPath.includes(`${resolve(ROOT_DIR, "plugin-sdk")}/`) ) { continue; } diff --git a/src/plugin-sdk/channel-runtime.ts b/src/plugin-sdk/channel-runtime.ts index 5e90b196c09..59832d70f80 100644 --- a/src/plugin-sdk/channel-runtime.ts +++ b/src/plugin-sdk/channel-runtime.ts @@ -43,6 +43,7 @@ export * from "../channels/plugins/whatsapp-heartbeat.js"; export * from "../infra/outbound/send-deps.js"; export * from "../polls.js"; export * from "../utils/message-channel.js"; +export * from "../whatsapp/normalize.js"; export { createActionGate, jsonResult, readStringParam } from "../agents/tools/common.js"; export * from "./channel-lifecycle.js"; export * from "./directory-runtime.js"; diff --git a/src/plugin-sdk/package-contract-guardrails.test.ts b/src/plugin-sdk/package-contract-guardrails.test.ts new file mode 100644 index 00000000000..046562708cd --- /dev/null +++ b/src/plugin-sdk/package-contract-guardrails.test.ts @@ -0,0 +1,145 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { dirname, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { pluginSdkEntrypoints } from "./entrypoints.js"; + +const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const REPO_ROOT = resolve(ROOT_DIR, ".."); +const REFERENCE_SCAN_ROOTS = ["src", "extensions", "scripts", "test", "docs"] as const; +const PLUGIN_SDK_SUBPATH_PATTERN = /openclaw\/plugin-sdk\/([a-z0-9][a-z0-9-]*)\b/g; + +function collectPluginSdkPackageExports(): string[] { + const packageJson = JSON.parse(readFileSync(resolve(REPO_ROOT, "package.json"), "utf8")) as { + exports?: Record; + }; + const exports = packageJson.exports ?? {}; + const subpaths: string[] = []; + for (const key of Object.keys(exports)) { + if (key === "./plugin-sdk") { + subpaths.push("index"); + continue; + } + if (!key.startsWith("./plugin-sdk/")) { + continue; + } + subpaths.push(key.slice("./plugin-sdk/".length)); + } + return subpaths.sort(); +} + +function collectPluginSdkSourceNames(): string[] { + const pluginSdkDir = resolve(REPO_ROOT, "src", "plugin-sdk"); + return readdirSync(pluginSdkDir, { withFileTypes: true }) + .filter( + (entry) => entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts"), + ) + .map((entry) => entry.name.slice(0, -".ts".length)) + .sort(); +} + +function collectTextFiles(rootRelativeDir: string): string[] { + const rootDir = resolve(REPO_ROOT, rootRelativeDir); + const files: string[] = []; + const stack = [rootDir]; + while (stack.length > 0) { + const current = stack.pop(); + if (!current) { + continue; + } + for (const entry of readdirSync(current, { withFileTypes: true })) { + const fullPath = resolve(current, entry.name); + if (entry.isDirectory()) { + if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage") { + continue; + } + stack.push(fullPath); + continue; + } + if (!entry.isFile()) { + continue; + } + if ( + /\.(?:[cm]?ts|[cm]?js|tsx|jsx|md|mdx|json)$/u.test(entry.name) && + !entry.name.endsWith(".snap") + ) { + files.push(fullPath); + } + } + } + return files; +} + +function collectPluginSdkSubpathReferences() { + const references: Array<{ file: string; subpath: string }> = []; + for (const rootRelativeDir of REFERENCE_SCAN_ROOTS) { + for (const fullPath of collectTextFiles(rootRelativeDir)) { + const source = readFileSync(fullPath, "utf8"); + for (const match of source.matchAll(PLUGIN_SDK_SUBPATH_PATTERN)) { + const subpath = match[1]; + if (!subpath) { + continue; + } + references.push({ + file: relative(REPO_ROOT, fullPath).replaceAll("\\", "/"), + subpath, + }); + } + } + } + return references; +} + +describe("plugin-sdk package contract guardrails", () => { + it("keeps package.json exports aligned with built plugin-sdk entrypoints", () => { + expect(collectPluginSdkPackageExports()).toEqual([...pluginSdkEntrypoints].sort()); + }); + + it("keeps repo openclaw/plugin-sdk/ references on exported built subpaths", () => { + const entrypoints = new Set(pluginSdkEntrypoints); + const exports = new Set(collectPluginSdkPackageExports()); + const failures: string[] = []; + + for (const reference of collectPluginSdkSubpathReferences()) { + const missingFrom: string[] = []; + if (!entrypoints.has(reference.subpath)) { + missingFrom.push("scripts/lib/plugin-sdk-entrypoints.json"); + } + if (!exports.has(reference.subpath)) { + missingFrom.push("package.json exports"); + } + if (missingFrom.length === 0) { + continue; + } + failures.push( + `${reference.file} references openclaw/plugin-sdk/${reference.subpath}, but ${reference.subpath} is missing from ${missingFrom.join(" and ")}`, + ); + } + + expect(failures).toEqual([]); + }); + + it("does not leave referenced src/plugin-sdk source names stranded outside the public contract", () => { + const exported = new Set(pluginSdkEntrypoints); + const references = collectPluginSdkSubpathReferences(); + const failures: string[] = []; + + for (const sourceName of collectPluginSdkSourceNames()) { + if (exported.has(sourceName) || sourceName === "compat" || sourceName === "index") { + continue; + } + const matchingRefs = references.filter((reference) => reference.subpath === sourceName); + if (matchingRefs.length === 0) { + continue; + } + failures.push( + `src/plugin-sdk/${sourceName}.ts is referenced as openclaw/plugin-sdk/${sourceName} in ${matchingRefs + .map((reference) => reference.file) + .sort() + .join(", ")}, but ${sourceName} is not exported as a public plugin-sdk subpath`, + ); + } + + expect(failures).toEqual([]); + }); +}); diff --git a/src/plugin-sdk/runtime-api-guardrails.test.ts b/src/plugin-sdk/runtime-api-guardrails.test.ts index 1b29d1570c6..b05bdf482f7 100644 --- a/src/plugin-sdk/runtime-api-guardrails.test.ts +++ b/src/plugin-sdk/runtime-api-guardrails.test.ts @@ -27,15 +27,25 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export * from "./src/send.js";', ], "extensions/imessage/runtime-api.ts": [ - 'export * from "./src/monitor.js";', - 'export * from "./src/probe.js";', - 'export * from "./src/send.js";', + 'export type { IMessageAccountConfig } from "../../src/config/types.imessage.js";', + 'export type { ChannelPlugin } from "../../src/channels/plugins/types.plugin.js";', + 'export { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, buildChannelConfigSchema, getChatChannelMeta } from "../../src/plugin-sdk/channel-plugin-common.js";', + 'export { formatTrimmedAllowFromEntries, resolveIMessageConfigAllowFrom, resolveIMessageConfigDefaultTo } from "../../src/plugin-sdk/channel-config-helpers.js";', + 'export { collectStatusIssuesFromLastError } from "../../src/plugin-sdk/status-helpers.js";', + 'export { resolveChannelMediaMaxBytes } from "../../src/channels/plugins/media-limits.js";', + 'export { looksLikeIMessageTargetId, normalizeIMessageMessagingTarget } from "../../src/channels/plugins/normalize/imessage.js";', + 'export { IMessageConfigSchema } from "../../src/config/zod-schema.providers-core.js";', + 'export { resolveIMessageGroupRequireMention, resolveIMessageGroupToolPolicy } from "./src/group-policy.js";', + 'export { monitorIMessageProvider } from "./src/monitor.js";', + 'export type { MonitorIMessageOpts } from "./src/monitor.js";', + 'export { probeIMessage } from "./src/probe.js";', + 'export { sendMessageIMessage } from "./src/send.js";', ], "extensions/googlechat/runtime-api.ts": ['export * from "openclaw/plugin-sdk/googlechat";'], "extensions/nextcloud-talk/runtime-api.ts": [ 'export * from "openclaw/plugin-sdk/nextcloud-talk";', ], - "extensions/signal/runtime-api.ts": ['export * from "./src/index.js";'], + "extensions/signal/runtime-api.ts": ['export * from "./src/runtime-api.js";'], "extensions/slack/runtime-api.ts": [ 'export * from "./src/action-runtime.js";', 'export * from "./src/directory-live.js";', @@ -44,14 +54,21 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export * from "./src/resolve-users.js";', ], "extensions/telegram/runtime-api.ts": [ - 'export * from "./src/audit.js";', - 'export * from "./src/action-runtime.js";', - 'export * from "./src/channel-actions.js";', - 'export * from "./src/monitor.js";', - 'export * from "./src/probe.js";', - 'export * from "./src/send.js";', - 'export * from "./src/thread-bindings.js";', - 'export * from "./src/token.js";', + 'export type { ChannelPlugin, OpenClawConfig, TelegramActionConfig } from "../../src/plugin-sdk/telegram-core.js";', + 'export type { ChannelMessageActionAdapter } from "../../src/channels/plugins/types.js";', + 'export type { TelegramAccountConfig, TelegramNetworkConfig } from "../../src/config/types.js";', + 'export type { OpenClawPluginApi, OpenClawPluginService, OpenClawPluginServiceContext, PluginLogger } from "../../src/plugins/types.js";', + 'export type { AcpRuntime, AcpRuntimeCapabilities, AcpRuntimeDoctorReport, AcpRuntimeEnsureInput, AcpRuntimeEvent, AcpRuntimeHandle, AcpRuntimeStatus, AcpRuntimeTurnInput, AcpSessionUpdateTag } from "../../src/acp/runtime/types.js";', + 'export type { AcpRuntimeErrorCode } from "../../src/acp/runtime/errors.js";', + 'export { AcpRuntimeError } from "../../src/acp/runtime/errors.js";', + 'export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../src/routing/session-key.js";', + 'export { buildChannelConfigSchema, getChatChannelMeta, jsonResult, readNumberParam, readReactionParams, readStringArrayParam, readStringOrNumberParam, readStringParam, resolvePollMaxSelections, TelegramConfigSchema } from "../../src/plugin-sdk/telegram-core.js";', + 'export { parseTelegramTopicConversation } from "../../src/acp/conversation-id.js";', + 'export { clearAccountEntryFields } from "../../src/channels/plugins/config-helpers.js";', + 'export { buildTokenChannelStatusSummary } from "../../src/plugin-sdk/status-helpers.js";', + 'export { projectCredentialSnapshotFields, resolveConfiguredFromCredentialStatuses } from "../../src/channels/account-snapshot-fields.js";', + 'export { resolveTelegramPollVisibility } from "../../src/poll-params.js";', + 'export { PAIRING_APPROVED_MESSAGE } from "../../src/channels/plugins/pairing-message.js";', ], "extensions/whatsapp/runtime-api.ts": [ 'export * from "./src/active-listener.js";', From 0dda3e66b5959e9f28ec21ae46771c9185fee0b5 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 17 Mar 2026 23:57:35 -0700 Subject: [PATCH 6/6] Plugin SDK: align docs and fix runtime imports --- docs/tools/plugin.md | 16 +++++----------- extensions/acpx/src/runtime-internals/process.ts | 4 ++-- .../google/media-understanding-provider.ts | 2 +- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index a66579c9328..a7c55626f1a 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -1144,22 +1144,16 @@ authoring plugins: - `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugin types and shared channel-facing helpers. Built-in WhatsApp implementation internals stay private to the bundled extension. - `openclaw/plugin-sdk/line` for LINE channel plugins. - `openclaw/plugin-sdk/msteams` for the bundled Microsoft Teams plugin surface. -- Bundled extension-specific subpaths are also available: +- Additional bundled extension-specific subpaths remain available where OpenClaw + intentionally exposes extension-facing helpers: `openclaw/plugin-sdk/acpx`, `openclaw/plugin-sdk/bluebubbles`, - `openclaw/plugin-sdk/copilot-proxy`, `openclaw/plugin-sdk/device-pair`, - `openclaw/plugin-sdk/diagnostics-otel`, `openclaw/plugin-sdk/diffs`, `openclaw/plugin-sdk/feishu`, `openclaw/plugin-sdk/googlechat`, - `openclaw/plugin-sdk/irc`, `openclaw/plugin-sdk/llm-task`, - `openclaw/plugin-sdk/lobster`, `openclaw/plugin-sdk/matrix`, + `openclaw/plugin-sdk/irc`, `openclaw/plugin-sdk/matrix`, `openclaw/plugin-sdk/mattermost`, `openclaw/plugin-sdk/memory-core`, - `openclaw/plugin-sdk/memory-lancedb`, `openclaw/plugin-sdk/minimax-portal-auth`, `openclaw/plugin-sdk/nextcloud-talk`, `openclaw/plugin-sdk/nostr`, - `openclaw/plugin-sdk/open-prose`, `openclaw/plugin-sdk/phone-control`, - `openclaw/plugin-sdk/qwen-portal-auth`, `openclaw/plugin-sdk/synology-chat`, - `openclaw/plugin-sdk/talk-voice`, `openclaw/plugin-sdk/test-utils`, - `openclaw/plugin-sdk/thread-ownership`, `openclaw/plugin-sdk/tlon`, - `openclaw/plugin-sdk/twitch`, `openclaw/plugin-sdk/voice-call`, + `openclaw/plugin-sdk/synology-chat`, `openclaw/plugin-sdk/test-utils`, + `openclaw/plugin-sdk/tlon`, `openclaw/plugin-sdk/twitch`, `openclaw/plugin-sdk/zalo`, and `openclaw/plugin-sdk/zalouser`. ## Channel target resolution diff --git a/extensions/acpx/src/runtime-internals/process.ts b/extensions/acpx/src/runtime-internals/process.ts index 4e2aa38a6d4..48e0bf274f2 100644 --- a/extensions/acpx/src/runtime-internals/process.ts +++ b/extensions/acpx/src/runtime-internals/process.ts @@ -5,14 +5,14 @@ import type { WindowsSpawnProgram, WindowsSpawnProgramCandidate, WindowsSpawnResolution, -} from "../runtime-api.js"; +} from "../../runtime-api.js"; import { applyWindowsSpawnProgramPolicy, listKnownProviderAuthEnvVarNames, materializeWindowsSpawnProgram, omitEnvKeysCaseInsensitive, resolveWindowsSpawnProgramCandidate, -} from "../runtime-api.js"; +} from "../../runtime-api.js"; export type SpawnExit = { code: number | null; diff --git a/extensions/google/media-understanding-provider.ts b/extensions/google/media-understanding-provider.ts index 73561b73ea3..7a6a519f8bc 100644 --- a/extensions/google/media-understanding-provider.ts +++ b/extensions/google/media-understanding-provider.ts @@ -10,7 +10,7 @@ import { type VideoDescriptionRequest, type VideoDescriptionResult, } from "openclaw/plugin-sdk/media-understanding"; -import { normalizeGoogleModelId, parseGeminiAuth } from "../runtime-api.js"; +import { normalizeGoogleModelId, parseGeminiAuth } from "./runtime-api.js"; export const DEFAULT_GOOGLE_AUDIO_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"; export const DEFAULT_GOOGLE_VIDEO_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";