mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix: share agent harness runtime activation (#67474)
This commit is contained in:
@@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Extensions/lmstudio: add exponential backoff to the inference-preload wrapper so an LM Studio model-load failure (for example the built-in memory guardrail rejecting a load because the swap is saturated) no longer produces a WARN line every ~2s for every chat request. The wrapper now records consecutive preload failures per `(baseUrl, modelKey, contextLength)` tuple with a 5s → 10s → 20s → … → 5min cooldown and skips the preload step entirely while a cooldown is active, letting chat requests proceed directly to the stream (the model is often already loaded via the LM Studio UI). The combined `preload failed` log line now reports consecutive-failure count and remaining cooldown so operators can act on the real issue instead of drowning in repeated warnings. (#67401) Thanks @xantorres.
|
||||
- Agents/replay: re-run tool/result pairing after strict replay tool-call ID sanitization on outbound requests so Anthropic-compatible providers like MiniMax no longer receive malformed orphan tool-result IDs such as `...toolresult1` during compaction and retry flows. (#67620) Thanks @stainlu.
|
||||
- Gateway/startup: fix spurious SIGUSR1 restart loop on Linux/systemd when plugin auto-enable is the only startup config write; the config hash guard was not captured for that write path, causing chokidar to treat each boot write as an external change and trigger a reload → restart cycle that corrupts manifest.db after repeated cycles. Fixes #67436. (#67557) thanks @openperf
|
||||
- Codex/harness: auto-enable the Codex plugin when `codex` is selected as an embedded agent harness runtime, including forced default, per-agent, and `OPENCLAW_AGENT_RUNTIME` paths. (#67474) Thanks @duqaXxX.
|
||||
- OpenAI Codex/CLI: keep resumed `codex exec resume` runs on the safe non-interactive path without reintroducing the removed dangerous bypass flag by passing the supported `--skip-git-repo-check` resume arg that real Codex CLI requires outside trusted git directories. (#67666) Thanks @plgonzalezrx8.
|
||||
- Codex/app-server: parse Desktop-originated app-server user agents such as `Codex Desktop/0.118.0`, keeping the version gate working when the Codex CLI inherits a multi-word originator. (#64666) Thanks @cyrusaf.
|
||||
- Cron/announce delivery: keep isolated announce `NO_REPLY` stripping case-insensitive across direct and text delivery, preserve structured media-only sends when a caption strips silent, and derive main-session awareness from the cleaned payloads so silent captions no longer leak stale `NO_REPLY` text. (#65016) Thanks @BKF-Gitty.
|
||||
|
||||
33
src/agents/harness-runtimes.ts
Normal file
33
src/agents/harness-runtimes.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
|
||||
export function collectConfiguredAgentHarnessRuntimes(
|
||||
config: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): string[] {
|
||||
const runtimes = new Set<string>();
|
||||
const pushRuntime = (value: unknown) => {
|
||||
if (typeof value !== "string") {
|
||||
return;
|
||||
}
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (!normalized || normalized === "auto" || normalized === "pi") {
|
||||
return;
|
||||
}
|
||||
runtimes.add(normalized);
|
||||
};
|
||||
|
||||
pushRuntime(config.agents?.defaults?.embeddedHarness?.runtime);
|
||||
if (Array.isArray(config.agents?.list)) {
|
||||
for (const agent of config.agents.list) {
|
||||
if (!isRecord(agent)) {
|
||||
continue;
|
||||
}
|
||||
pushRuntime((agent.embeddedHarness as Record<string, unknown> | undefined)?.runtime);
|
||||
}
|
||||
}
|
||||
pushRuntime(env.OPENCLAW_AGENT_RUNTIME);
|
||||
|
||||
return [...runtimes].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
@@ -247,6 +247,27 @@ describe("applyPluginAutoEnable core", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("auto-enables an opt-in plugin when an agent harness runtime is forced by env", () => {
|
||||
const result = applyPluginAutoEnable({
|
||||
config: {},
|
||||
env: makeIsolatedEnv({ OPENCLAW_AGENT_RUNTIME: "codex" }),
|
||||
manifestRegistry: makeRegistry([
|
||||
{
|
||||
id: "codex",
|
||||
channels: [],
|
||||
activation: {
|
||||
onAgentHarnesses: ["codex"],
|
||||
},
|
||||
},
|
||||
]),
|
||||
});
|
||||
|
||||
expect(result.config.plugins?.entries?.codex?.enabled).toBe(true);
|
||||
expect(result.changes).toContain(
|
||||
"codex agent harness runtime configured, enabled automatically.",
|
||||
);
|
||||
});
|
||||
|
||||
it("skips auto-enable work for configs without channel or plugin-owned surfaces", () => {
|
||||
const result = applyPluginAutoEnable({
|
||||
config: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { collectConfiguredAgentHarnessRuntimes } from "../agents/harness-runtimes.js";
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import {
|
||||
hasPotentialConfiguredChannels,
|
||||
@@ -99,35 +100,8 @@ function extractProviderFromModelRef(value: string): string | null {
|
||||
return normalizeProviderId(trimmed.slice(0, slash));
|
||||
}
|
||||
|
||||
function collectEmbeddedHarnessRuntimes(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): string[] {
|
||||
const runtimes = new Set<string>();
|
||||
const pushRuntime = (value: unknown) => {
|
||||
if (typeof value !== "string") {
|
||||
return;
|
||||
}
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (!normalized || normalized === "auto" || normalized === "pi") {
|
||||
return;
|
||||
}
|
||||
runtimes.add(normalized);
|
||||
};
|
||||
|
||||
pushRuntime(cfg.agents?.defaults?.embeddedHarness?.runtime);
|
||||
if (Array.isArray(cfg.agents?.list)) {
|
||||
for (const agent of cfg.agents.list) {
|
||||
if (!isRecord(agent)) {
|
||||
continue;
|
||||
}
|
||||
pushRuntime((agent.embeddedHarness as Record<string, unknown> | undefined)?.runtime);
|
||||
}
|
||||
}
|
||||
pushRuntime(env.OPENCLAW_AGENT_RUNTIME);
|
||||
|
||||
return [...runtimes].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function hasConfiguredEmbeddedHarnessRuntime(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean {
|
||||
return collectEmbeddedHarnessRuntimes(cfg, env).length > 0;
|
||||
return collectConfiguredAgentHarnessRuntimes(cfg, env).length > 0;
|
||||
}
|
||||
|
||||
function resolveAgentHarnessOwnerPluginIds(
|
||||
@@ -490,7 +464,7 @@ export function resolveConfiguredPluginAutoEnableCandidates(params: {
|
||||
}
|
||||
}
|
||||
|
||||
for (const runtime of collectEmbeddedHarnessRuntimes(params.config, params.env)) {
|
||||
for (const runtime of collectConfiguredAgentHarnessRuntimes(params.config, params.env)) {
|
||||
const pluginIds = resolveAgentHarnessOwnerPluginIds(params.registry, runtime);
|
||||
for (const pluginId of pluginIds) {
|
||||
changes.push({
|
||||
|
||||
@@ -144,6 +144,7 @@ function createManifestRegistryFixture() {
|
||||
function expectStartupPluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
expected: readonly string[];
|
||||
}) {
|
||||
expect(
|
||||
@@ -153,7 +154,7 @@ function expectStartupPluginIds(params: {
|
||||
? { activationSourceConfig: params.activationSourceConfig }
|
||||
: {}),
|
||||
workspaceDir: "/tmp",
|
||||
env: process.env,
|
||||
env: params.env ?? process.env,
|
||||
}),
|
||||
).toEqual(params.expected);
|
||||
expect(loadPluginManifestRegistry).toHaveBeenCalled();
|
||||
@@ -162,6 +163,7 @@ function expectStartupPluginIds(params: {
|
||||
function expectStartupPluginIdsCase(params: {
|
||||
config: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
expected: readonly string[];
|
||||
}) {
|
||||
expectStartupPluginIds(params);
|
||||
@@ -278,7 +280,7 @@ function createStartupConfig(params: {
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
: {}),
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
@@ -421,6 +423,16 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("includes required agent harness owner plugins when env forces the runtime", () => {
|
||||
expectStartupPluginIdsCase({
|
||||
config: createStartupConfig({
|
||||
enabledPluginIds: ["codex"],
|
||||
}),
|
||||
env: { OPENCLAW_AGENT_RUNTIME: "codex" },
|
||||
expected: ["demo-channel", "browser", "codex"],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not include required agent harness owner plugins when they are explicitly disabled", () => {
|
||||
expectStartupPluginIdsCase({
|
||||
config: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { collectConfiguredAgentHarnessRuntimes } from "../agents/harness-runtimes.js";
|
||||
import { listPotentialConfiguredChannelIds } from "../channels/config-presence.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
@@ -50,33 +51,6 @@ function dedupeSortedPluginIds(values: Iterable<string>): string[] {
|
||||
return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function collectRequestedAgentHarnessRuntimes(
|
||||
config: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): string[] {
|
||||
const runtimes = new Set<string>();
|
||||
const pushRuntime = (value: unknown) => {
|
||||
const normalized = typeof value === "string" ? normalizeOptionalLowercaseString(value) : null;
|
||||
if (!normalized || normalized === "auto" || normalized === "pi") {
|
||||
return;
|
||||
}
|
||||
runtimes.add(normalized);
|
||||
};
|
||||
|
||||
pushRuntime(config.agents?.defaults?.embeddedHarness?.runtime);
|
||||
if (Array.isArray(config.agents?.list)) {
|
||||
for (const entry of config.agents.list) {
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
continue;
|
||||
}
|
||||
pushRuntime((entry as { embeddedHarness?: { runtime?: string } }).embeddedHarness?.runtime);
|
||||
}
|
||||
}
|
||||
pushRuntime(env.OPENCLAW_AGENT_RUNTIME);
|
||||
|
||||
return [...runtimes].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function normalizeChannelIds(channelIds: Iterable<string>): string[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
@@ -300,19 +274,21 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
config: params.activationSourceConfig ?? params.config,
|
||||
});
|
||||
const requiredAgentHarnessPluginIds = new Set(
|
||||
collectRequestedAgentHarnessRuntimes(params.activationSourceConfig ?? params.config, params.env)
|
||||
.flatMap((runtime) =>
|
||||
resolveManifestActivationPluginIds({
|
||||
trigger: {
|
||||
kind: "agentHarness",
|
||||
runtime,
|
||||
},
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
cache: true,
|
||||
}),
|
||||
),
|
||||
collectConfiguredAgentHarnessRuntimes(
|
||||
params.activationSourceConfig ?? params.config,
|
||||
params.env,
|
||||
).flatMap((runtime) =>
|
||||
resolveManifestActivationPluginIds({
|
||||
trigger: {
|
||||
kind: "agentHarness",
|
||||
runtime,
|
||||
},
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
cache: true,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const startupDreamingPluginIds = resolveGatewayStartupDreamingPluginIds(params.config);
|
||||
const explicitMemorySlotStartupPluginId = resolveExplicitMemorySlotStartupPluginId(
|
||||
|
||||
Reference in New Issue
Block a user