mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
fix(gateway): keep effective tools on hot registry path
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Memory-core: re-resolve the active runtime config whenever `memory_search` or `memory_get` executes, so provider changes made by `config.patch` stop leaving stale embedding backends behind in existing tool instances. Fixes #61098. Thanks @BradGroux and @Linux2010.
|
||||
- WebChat: keep bare `/new` and `/reset` startup instructions out of visible chat history while preserving `/reset <note>` as user-visible transcript text. Fixes #72369. Thanks @collynes and @haishmg.
|
||||
- CLI/doctor: remove dangling channel config, heartbeat targets, and channel model overrides when stale plugin repair removes a missing channel plugin, preventing Gateway boot loops after failed plugin reinstalls. Fixes #65293. Thanks @yidecode.
|
||||
- Control UI/Gateway: reuse the gateway-bound plugin registry and avoid model/auth discovery while resolving effective tool inventory, so chat runs no longer stall Control UI requests on repeated plugin/model setup. Fixes #72365; supersedes #72558. Thanks @Gabiii2398 and @1yihui.
|
||||
- Channels/setup: treat bundled channel plugins as already bundled during `channels add` and onboarding, enabling them without writing redundant `plugins.load.paths` entries or path install records. Fixes #72740. Thanks @iCodePoet.
|
||||
- WhatsApp: honor gateway `HTTPS_PROXY` / `HTTP_PROXY` env vars for QR-login WebSocket connections, while respecting `NO_PROXY`, so proxied networks no longer fall back to direct `mmg.whatsapp.net` connections that time out with 408. Fixes #72547; supersedes #72692. Thanks @mebusw and @SymbolStar.
|
||||
- Bonjour: default mDNS advertisements to the system hostname when it is DNS-safe, avoiding `openclaw.local` probing conflicts and Gateway restart loops on hosts such as `Lobster` or `ubuntu`. Fixes #72355 and #72689; supersedes #72694. Thanks @mscheuerlein-bot, @gcusms, @moyuwuhen601, @pavan987, @zml-0912, @hhq365, and @SymbolStar.
|
||||
|
||||
@@ -54,6 +54,7 @@ export function resolveOpenClawPluginToolsForOptions(params: {
|
||||
}),
|
||||
existingToolNames: params.existingToolNames ?? new Set<string>(),
|
||||
toolAllowlist: params.options?.pluginToolAllowlist,
|
||||
allowGatewaySubagentBinding: params.options?.allowGatewaySubagentBinding,
|
||||
});
|
||||
|
||||
return applyPluginToolDeliveryDefaults({
|
||||
|
||||
@@ -120,6 +120,26 @@ describe("createOpenClawTools browser plugin integration", () => {
|
||||
expect(details.workspaceOnly).toBe(true);
|
||||
});
|
||||
|
||||
it("forwards gateway subagent binding to plugin resolution", () => {
|
||||
hoisted.resolvePluginTools.mockReturnValue([]);
|
||||
const config = {
|
||||
plugins: {
|
||||
allow: ["browser"],
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
resolveOpenClawPluginToolsForOptions({
|
||||
options: { config, allowGatewaySubagentBinding: true },
|
||||
resolvedConfig: config,
|
||||
});
|
||||
|
||||
expect(hoisted.resolvePluginTools).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
allowGatewaySubagentBinding: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not pass a stale active snapshot as plugin runtime config for a resolved run config", () => {
|
||||
const staleSourceConfig = {
|
||||
plugins: {
|
||||
|
||||
@@ -23,7 +23,6 @@ const effectiveInventoryState = vi.hoisted(() => ({
|
||||
pluginMeta: {} as Record<string, { pluginId: string } | undefined>,
|
||||
channelMeta: {} as Record<string, { channelId: string } | undefined>,
|
||||
effectivePolicy: {} as { profile?: string; providerProfile?: string },
|
||||
resolvedModelCompat: undefined as Record<string, unknown> | undefined,
|
||||
createToolsMock: vi.fn<typeof createOpenClawCodingTools>(
|
||||
(_options) =>
|
||||
[
|
||||
@@ -48,16 +47,6 @@ vi.mock("./pi-tools.js", () => ({
|
||||
effectiveInventoryState.createToolsMock(options),
|
||||
}));
|
||||
|
||||
vi.mock("./pi-embedded-runner/model.js", () => ({
|
||||
resolveModel: vi.fn(() => ({
|
||||
model: effectiveInventoryState.resolvedModelCompat
|
||||
? { compat: effectiveInventoryState.resolvedModelCompat }
|
||||
: undefined,
|
||||
authStorage: {} as never,
|
||||
modelRegistry: {} as never,
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/tools.js", () => ({
|
||||
getPluginToolMeta: (tool: { name: string }) => effectiveInventoryState.pluginMeta[tool.name],
|
||||
}));
|
||||
@@ -79,7 +68,6 @@ async function loadHarness(options?: {
|
||||
pluginMeta?: Record<string, { pluginId: string } | undefined>;
|
||||
channelMeta?: Record<string, { channelId: string } | undefined>;
|
||||
effectivePolicy?: { profile?: string; providerProfile?: string };
|
||||
resolvedModelCompat?: Record<string, unknown>;
|
||||
}) {
|
||||
effectiveInventoryState.tools = options?.tools ?? [
|
||||
mockTool({ name: "exec", label: "Exec", description: "Run shell commands" }),
|
||||
@@ -88,7 +76,6 @@ async function loadHarness(options?: {
|
||||
effectiveInventoryState.pluginMeta = options?.pluginMeta ?? {};
|
||||
effectiveInventoryState.channelMeta = options?.channelMeta ?? {};
|
||||
effectiveInventoryState.effectivePolicy = options?.effectivePolicy ?? {};
|
||||
effectiveInventoryState.resolvedModelCompat = options?.resolvedModelCompat;
|
||||
effectiveInventoryState.createToolsMock =
|
||||
options?.createToolsMock ??
|
||||
vi.fn<typeof createOpenClawCodingTools>((_options) => effectiveInventoryState.tools);
|
||||
@@ -111,7 +98,6 @@ describe("resolveEffectiveToolInventory", () => {
|
||||
effectiveInventoryState.pluginMeta = {};
|
||||
effectiveInventoryState.channelMeta = {};
|
||||
effectiveInventoryState.effectivePolicy = {};
|
||||
effectiveInventoryState.resolvedModelCompat = undefined;
|
||||
effectiveInventoryState.createToolsMock = vi.fn<typeof createOpenClawCodingTools>(
|
||||
(_options) => effectiveInventoryState.tools,
|
||||
);
|
||||
@@ -312,11 +298,31 @@ describe("resolveEffectiveToolInventory", () => {
|
||||
]);
|
||||
const { resolveEffectiveToolInventory } = await loadHarness({
|
||||
createToolsMock,
|
||||
resolvedModelCompat: { supportsTools: true, supportsNativeWebSearch: true },
|
||||
});
|
||||
|
||||
resolveEffectiveToolInventory({
|
||||
cfg: {},
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
xai: {
|
||||
baseUrl: "https://api.x.ai/v1",
|
||||
models: [
|
||||
{
|
||||
id: "grok-test",
|
||||
name: "Grok Test",
|
||||
api: "openai-completions",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 8_192,
|
||||
compat: { supportsTools: true, nativeWebSearchTool: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
agentDir: "/tmp/agents/main/agent",
|
||||
modelProvider: "xai",
|
||||
modelId: "grok-test",
|
||||
@@ -325,7 +331,7 @@ describe("resolveEffectiveToolInventory", () => {
|
||||
expect(createToolsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
allowGatewaySubagentBinding: true,
|
||||
modelCompat: { supportsTools: true, supportsNativeWebSearch: true },
|
||||
modelCompat: { supportsTools: true, nativeWebSearchTool: true },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,9 +7,10 @@ import {
|
||||
} from "../shared/string-coerce.js";
|
||||
import { resolveAgentDir, resolveAgentWorkspaceDir, resolveSessionAgentId } from "./agent-scope.js";
|
||||
import { getChannelAgentToolMeta } from "./channel-tools.js";
|
||||
import { resolveModel } from "./pi-embedded-runner/model.js";
|
||||
import { normalizeStaticProviderModelId } from "./model-ref-shared.js";
|
||||
import { createOpenClawCodingTools } from "./pi-tools.js";
|
||||
import { resolveEffectiveToolPolicy } from "./pi-tools.policy.js";
|
||||
import { findNormalizedProviderValue, normalizeProviderId } from "./provider-id.js";
|
||||
import { summarizeToolDescriptionText } from "./tool-description-summary.js";
|
||||
import { resolveToolDisplay } from "./tool-display.js";
|
||||
import { normalizeToolName } from "./tool-policy.js";
|
||||
@@ -164,20 +165,30 @@ function disambiguateLabels(entries: EffectiveToolInventoryEntry[]): EffectiveTo
|
||||
|
||||
function resolveEffectiveModelCompat(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentDir: string;
|
||||
modelProvider?: string;
|
||||
modelId?: string;
|
||||
}) {
|
||||
const provider = params.modelProvider?.trim();
|
||||
const modelId = params.modelId?.trim();
|
||||
const provider = normalizeProviderId(params.modelProvider ?? "");
|
||||
const modelId = params.modelId?.trim() ?? "";
|
||||
if (!provider || !modelId) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return extractModelCompat(resolveModel(provider, modelId, params.agentDir, params.cfg).model);
|
||||
} catch {
|
||||
const providerConfig = findNormalizedProviderValue(params.cfg.models?.providers, provider);
|
||||
const models = Array.isArray(providerConfig?.models) ? providerConfig.models : [];
|
||||
if (models.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const normalizedModelId = normalizeStaticProviderModelId(provider, modelId);
|
||||
const normalizedModelKey = normalizeLowercaseStringOrEmpty(normalizedModelId);
|
||||
const providerPrefixedModelKey = normalizeLowercaseStringOrEmpty(
|
||||
`${provider}/${normalizedModelId}`,
|
||||
);
|
||||
const match = models.find((model) => {
|
||||
const id = normalizeStaticProviderModelId(provider, model.id);
|
||||
const key = normalizeLowercaseStringOrEmpty(id);
|
||||
return key === normalizedModelKey || key === providerPrefixedModelKey;
|
||||
});
|
||||
return extractModelCompat(match);
|
||||
}
|
||||
|
||||
export function resolveEffectiveToolInventory(
|
||||
@@ -190,7 +201,6 @@ export function resolveEffectiveToolInventory(
|
||||
const agentDir = params.agentDir ?? resolveAgentDir(params.cfg, agentId);
|
||||
const modelCompat = resolveEffectiveModelCompat({
|
||||
cfg: params.cfg,
|
||||
agentDir,
|
||||
modelProvider: params.modelProvider,
|
||||
modelId: params.modelId,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user