Skip to content

Commit 86f1084

Browse files
committed
fix: share agent harness runtime activation (#67474)
1 parent f4bbd01 commit 86f1084

6 files changed

Lines changed: 88 additions & 71 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai
4242
- 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.
4343
- 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.
4444
- 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
45+
- 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.
4546
- 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.
4647
- 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.
4748
- 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.

src/agents/harness-runtimes.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { OpenClawConfig } from "../config/types.openclaw.js";
2+
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
3+
import { isRecord } from "../utils.js";
4+
5+
export function collectConfiguredAgentHarnessRuntimes(
6+
config: OpenClawConfig,
7+
env: NodeJS.ProcessEnv,
8+
): string[] {
9+
const runtimes = new Set<string>();
10+
const pushRuntime = (value: unknown) => {
11+
if (typeof value !== "string") {
12+
return;
13+
}
14+
const normalized = normalizeOptionalLowercaseString(value);
15+
if (!normalized || normalized === "auto" || normalized === "pi") {
16+
return;
17+
}
18+
runtimes.add(normalized);
19+
};
20+
21+
pushRuntime(config.agents?.defaults?.embeddedHarness?.runtime);
22+
if (Array.isArray(config.agents?.list)) {
23+
for (const agent of config.agents.list) {
24+
if (!isRecord(agent)) {
25+
continue;
26+
}
27+
pushRuntime((agent.embeddedHarness as Record<string, unknown> | undefined)?.runtime);
28+
}
29+
}
30+
pushRuntime(env.OPENCLAW_AGENT_RUNTIME);
31+
32+
return [...runtimes].toSorted((left, right) => left.localeCompare(right));
33+
}

src/config/plugin-auto-enable.core.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,27 @@ describe("applyPluginAutoEnable core", () => {
247247
);
248248
});
249249

250+
it("auto-enables an opt-in plugin when an agent harness runtime is forced by env", () => {
251+
const result = applyPluginAutoEnable({
252+
config: {},
253+
env: makeIsolatedEnv({ OPENCLAW_AGENT_RUNTIME: "codex" }),
254+
manifestRegistry: makeRegistry([
255+
{
256+
id: "codex",
257+
channels: [],
258+
activation: {
259+
onAgentHarnesses: ["codex"],
260+
},
261+
},
262+
]),
263+
});
264+
265+
expect(result.config.plugins?.entries?.codex?.enabled).toBe(true);
266+
expect(result.changes).toContain(
267+
"codex agent harness runtime configured, enabled automatically.",
268+
);
269+
});
270+
250271
it("skips auto-enable work for configs without channel or plugin-owned surfaces", () => {
251272
const result = applyPluginAutoEnable({
252273
config: {

src/config/plugin-auto-enable.shared.ts

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { collectConfiguredAgentHarnessRuntimes } from "../agents/harness-runtimes.js";
12
import { normalizeProviderId } from "../agents/provider-id.js";
23
import {
34
hasPotentialConfiguredChannels,
@@ -99,35 +100,8 @@ function extractProviderFromModelRef(value: string): string | null {
99100
return normalizeProviderId(trimmed.slice(0, slash));
100101
}
101102

102-
function collectEmbeddedHarnessRuntimes(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): string[] {
103-
const runtimes = new Set<string>();
104-
const pushRuntime = (value: unknown) => {
105-
if (typeof value !== "string") {
106-
return;
107-
}
108-
const normalized = normalizeOptionalLowercaseString(value);
109-
if (!normalized || normalized === "auto" || normalized === "pi") {
110-
return;
111-
}
112-
runtimes.add(normalized);
113-
};
114-
115-
pushRuntime(cfg.agents?.defaults?.embeddedHarness?.runtime);
116-
if (Array.isArray(cfg.agents?.list)) {
117-
for (const agent of cfg.agents.list) {
118-
if (!isRecord(agent)) {
119-
continue;
120-
}
121-
pushRuntime((agent.embeddedHarness as Record<string, unknown> | undefined)?.runtime);
122-
}
123-
}
124-
pushRuntime(env.OPENCLAW_AGENT_RUNTIME);
125-
126-
return [...runtimes].toSorted((left, right) => left.localeCompare(right));
127-
}
128-
129103
function hasConfiguredEmbeddedHarnessRuntime(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean {
130-
return collectEmbeddedHarnessRuntimes(cfg, env).length > 0;
104+
return collectConfiguredAgentHarnessRuntimes(cfg, env).length > 0;
131105
}
132106

133107
function resolveAgentHarnessOwnerPluginIds(
@@ -490,7 +464,7 @@ export function resolveConfiguredPluginAutoEnableCandidates(params: {
490464
}
491465
}
492466

493-
for (const runtime of collectEmbeddedHarnessRuntimes(params.config, params.env)) {
467+
for (const runtime of collectConfiguredAgentHarnessRuntimes(params.config, params.env)) {
494468
const pluginIds = resolveAgentHarnessOwnerPluginIds(params.registry, runtime);
495469
for (const pluginId of pluginIds) {
496470
changes.push({

src/plugins/channel-plugin-ids.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ function createManifestRegistryFixture() {
144144
function expectStartupPluginIds(params: {
145145
config: OpenClawConfig;
146146
activationSourceConfig?: OpenClawConfig;
147+
env?: NodeJS.ProcessEnv;
147148
expected: readonly string[];
148149
}) {
149150
expect(
@@ -153,7 +154,7 @@ function expectStartupPluginIds(params: {
153154
? { activationSourceConfig: params.activationSourceConfig }
154155
: {}),
155156
workspaceDir: "/tmp",
156-
env: process.env,
157+
env: params.env ?? process.env,
157158
}),
158159
).toEqual(params.expected);
159160
expect(loadPluginManifestRegistry).toHaveBeenCalled();
@@ -162,6 +163,7 @@ function expectStartupPluginIds(params: {
162163
function expectStartupPluginIdsCase(params: {
163164
config: OpenClawConfig;
164165
activationSourceConfig?: OpenClawConfig;
166+
env?: NodeJS.ProcessEnv;
165167
expected: readonly string[];
166168
}) {
167169
expectStartupPluginIds(params);
@@ -278,7 +280,7 @@ function createStartupConfig(params: {
278280
: {}),
279281
},
280282
}
281-
: {}),
283+
: {}),
282284
} as OpenClawConfig;
283285
}
284286

@@ -421,6 +423,16 @@ describe("resolveGatewayStartupPluginIds", () => {
421423
});
422424
});
423425

426+
it("includes required agent harness owner plugins when env forces the runtime", () => {
427+
expectStartupPluginIdsCase({
428+
config: createStartupConfig({
429+
enabledPluginIds: ["codex"],
430+
}),
431+
env: { OPENCLAW_AGENT_RUNTIME: "codex" },
432+
expected: ["demo-channel", "browser", "codex"],
433+
});
434+
});
435+
424436
it("does not include required agent harness owner plugins when they are explicitly disabled", () => {
425437
expectStartupPluginIdsCase({
426438
config: {

src/plugins/channel-plugin-ids.ts

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { collectConfiguredAgentHarnessRuntimes } from "../agents/harness-runtimes.js";
12
import { listPotentialConfiguredChannelIds } from "../channels/config-presence.js";
23
import type { OpenClawConfig } from "../config/types.openclaw.js";
34
import {
@@ -50,33 +51,6 @@ function dedupeSortedPluginIds(values: Iterable<string>): string[] {
5051
return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
5152
}
5253

53-
function collectRequestedAgentHarnessRuntimes(
54-
config: OpenClawConfig,
55-
env: NodeJS.ProcessEnv,
56-
): string[] {
57-
const runtimes = new Set<string>();
58-
const pushRuntime = (value: unknown) => {
59-
const normalized = typeof value === "string" ? normalizeOptionalLowercaseString(value) : null;
60-
if (!normalized || normalized === "auto" || normalized === "pi") {
61-
return;
62-
}
63-
runtimes.add(normalized);
64-
};
65-
66-
pushRuntime(config.agents?.defaults?.embeddedHarness?.runtime);
67-
if (Array.isArray(config.agents?.list)) {
68-
for (const entry of config.agents.list) {
69-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
70-
continue;
71-
}
72-
pushRuntime((entry as { embeddedHarness?: { runtime?: string } }).embeddedHarness?.runtime);
73-
}
74-
}
75-
pushRuntime(env.OPENCLAW_AGENT_RUNTIME);
76-
77-
return [...runtimes].toSorted((left, right) => left.localeCompare(right));
78-
}
79-
8054
function normalizeChannelIds(channelIds: Iterable<string>): string[] {
8155
return Array.from(
8256
new Set(
@@ -300,19 +274,21 @@ export function resolveGatewayStartupPluginIds(params: {
300274
config: params.activationSourceConfig ?? params.config,
301275
});
302276
const requiredAgentHarnessPluginIds = new Set(
303-
collectRequestedAgentHarnessRuntimes(params.activationSourceConfig ?? params.config, params.env)
304-
.flatMap((runtime) =>
305-
resolveManifestActivationPluginIds({
306-
trigger: {
307-
kind: "agentHarness",
308-
runtime,
309-
},
310-
config: params.config,
311-
workspaceDir: params.workspaceDir,
312-
env: params.env,
313-
cache: true,
314-
}),
315-
),
277+
collectConfiguredAgentHarnessRuntimes(
278+
params.activationSourceConfig ?? params.config,
279+
params.env,
280+
).flatMap((runtime) =>
281+
resolveManifestActivationPluginIds({
282+
trigger: {
283+
kind: "agentHarness",
284+
runtime,
285+
},
286+
config: params.config,
287+
workspaceDir: params.workspaceDir,
288+
env: params.env,
289+
cache: true,
290+
}),
291+
),
316292
);
317293
const startupDreamingPluginIds = resolveGatewayStartupDreamingPluginIds(params.config);
318294
const explicitMemorySlotStartupPluginId = resolveExplicitMemorySlotStartupPluginId(

0 commit comments

Comments
 (0)