Plugins: broaden plugin surface for Codex App Server (#45318)

* Plugins: add inbound claim and Telegram interaction seams

* Plugins: add Discord interaction surface

* Chore: fix formatting after plugin rebase

* fix(hooks): preserve observers after inbound claim

* test(hooks): cover claimed inbound observer delivery

* fix(plugins): harden typing lease refreshes

* fix(discord): pass real auth to plugin interactions

* fix(plugins): remove raw session binding runtime exposure

* fix(plugins): tighten interactive callback handling

* Plugins: gate conversation binding with approvals

* Plugins: migrate legacy plugin binding records

* Plugins/phone-control: update test command context

* Plugins: migrate legacy binding ids

* Plugins: migrate legacy codex session bindings

* Discord: fix plugin interaction handling

* Discord: support direct plugin conversation binds

* Plugins: preserve Discord command bind targets

* Tests: fix plugin binding and interactive fallout

* Discord: stabilize directory lookup tests

* Discord: route bound DMs to plugins

* Discord: restore plugin bindings after restart

* Telegram: persist detached plugin bindings

* Plugins: limit binding APIs to Telegram and Discord

* Plugins: harden bound conversation routing

* Plugins: fix extension target imports

* Plugins: fix Telegram runtime extension imports

* Plugins: format rebased binding handlers

* Discord: bind group DM interactions by channel

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Harold Hunt
2026-03-15 19:06:11 -04:00
committed by GitHub
parent 4eee827dce
commit aa1454d1a8
53 changed files with 5322 additions and 123 deletions

View File

@@ -13,6 +13,7 @@ import { resolveUserPath } from "../utils.js";
import { registerPluginCommand } from "./commands.js";
import { normalizePluginHttpPath } from "./http-path.js";
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
import { registerPluginInteractiveHandler } from "./interactive.js";
import { normalizeRegisteredProvider } from "./provider-validation.js";
import type { PluginRuntime } from "./runtime/types.js";
import { defaultSlotIdForKey } from "./slots.js";
@@ -47,17 +48,21 @@ import type {
export type PluginToolRegistration = {
pluginId: string;
pluginName?: string;
factory: OpenClawPluginToolFactory;
names: string[];
optional: boolean;
source: string;
rootDir?: string;
};
export type PluginCliRegistration = {
pluginId: string;
pluginName?: string;
register: OpenClawPluginCliRegistrar;
commands: string[];
source: string;
rootDir?: string;
};
export type PluginHttpRouteRegistration = {
@@ -71,15 +76,19 @@ export type PluginHttpRouteRegistration = {
export type PluginChannelRegistration = {
pluginId: string;
pluginName?: string;
plugin: ChannelPlugin;
dock?: ChannelDock;
source: string;
rootDir?: string;
};
export type PluginProviderRegistration = {
pluginId: string;
pluginName?: string;
provider: ProviderPlugin;
source: string;
rootDir?: string;
};
export type PluginHookRegistration = {
@@ -87,18 +96,23 @@ export type PluginHookRegistration = {
entry: HookEntry;
events: string[];
source: string;
rootDir?: string;
};
export type PluginServiceRegistration = {
pluginId: string;
pluginName?: string;
service: OpenClawPluginService;
source: string;
rootDir?: string;
};
export type PluginCommandRegistration = {
pluginId: string;
pluginName?: string;
command: OpenClawPluginCommandDefinition;
source: string;
rootDir?: string;
};
export type PluginRecord = {
@@ -108,6 +122,7 @@ export type PluginRecord = {
description?: string;
kind?: PluginKind;
source: string;
rootDir?: string;
origin: PluginOrigin;
workspaceDir?: string;
enabled: boolean;
@@ -212,10 +227,12 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
}
registry.tools.push({
pluginId: record.id,
pluginName: record.name,
factory,
names: normalized,
optional,
source: record.source,
rootDir: record.rootDir,
});
};
@@ -443,9 +460,11 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
record.channelIds.push(id);
registry.channels.push({
pluginId: record.id,
pluginName: record.name,
plugin,
dock: normalized.dock,
source: record.source,
rootDir: record.rootDir,
});
};
@@ -473,8 +492,10 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
record.providerIds.push(id);
registry.providers.push({
pluginId: record.id,
pluginName: record.name,
provider: normalizedProvider,
source: record.source,
rootDir: record.rootDir,
});
};
@@ -509,9 +530,11 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
record.cliCommands.push(...commands);
registry.cliRegistrars.push({
pluginId: record.id,
pluginName: record.name,
register: registrar,
commands,
source: record.source,
rootDir: record.rootDir,
});
};
@@ -533,8 +556,10 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
record.services.push(id);
registry.services.push({
pluginId: record.id,
pluginName: record.name,
service,
source: record.source,
rootDir: record.rootDir,
});
};
@@ -551,7 +576,10 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
}
// Register with the plugin command system (validates name and checks for duplicates)
const result = registerPluginCommand(record.id, command);
const result = registerPluginCommand(record.id, command, {
pluginName: record.name,
pluginRoot: record.rootDir,
});
if (!result.ok) {
pushDiagnostic({
level: "error",
@@ -565,8 +593,10 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
record.commands.push(name);
registry.commands.push({
pluginId: record.id,
pluginName: record.name,
command,
source: record.source,
rootDir: record.rootDir,
});
};
@@ -640,6 +670,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
version: record.version,
description: record.description,
source: record.source,
rootDir: record.rootDir,
config: params.config,
pluginConfig: params.pluginConfig,
runtime: registryParams.runtime,
@@ -653,6 +684,20 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
registerGatewayMethod: (method, handler) => registerGatewayMethod(record, method, handler),
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
registerService: (service) => registerService(record, service),
registerInteractiveHandler: (registration) => {
const result = registerPluginInteractiveHandler(record.id, registration, {
pluginName: record.name,
pluginRoot: record.rootDir,
});
if (!result.ok) {
pushDiagnostic({
level: "warn",
pluginId: record.id,
source: record.source,
message: result.error ?? "interactive handler registration failed",
});
}
},
registerCommand: (command) => registerCommand(record, command),
registerContextEngine: (id, factory) => {
if (id === defaultSlotIdForKey("contextEngine")) {