extensions/acpx: expose probeAgent config so non-codex ACP stacks sta… · openclaw/openclaw@eab26ac
lyfuci
·
2026-04-23
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai
|
40 | 40 | - Providers/OpenAI: lock the auth picker wording for OpenAI API key, Codex browser login, and Codex device pairing so the setup choices no longer imply a mixed Codex/API-key auth path. (#67848) Thanks @tmlxrd. |
41 | 41 | - Agents/BTW: route `/btw` side questions through provider stream registration with the session workspace, so Ollama provider URL construction and workspace-scoped hooks apply correctly. Fixes #68336. (#70413) Thanks @suboss87. |
42 | 42 | - Agents/sessions: make session transcript write locks non-reentrant by default, so same-process transcript writers contend unless a helper explicitly opts into nested lock ownership. |
| 43 | +- ACPX/probe: expose an optional `probeAgent` plugin config field so the embedded ACP runtime health probe can target a configured agent (for example `opencode` or `claude`) instead of hardcoding `codex`, and stop marking the entire ACP runtime backend unavailable when the default probe agent is simply not installed or not authenticated. (#68409) Thanks @lyfuci. |
43 | 44 | - Memory search: use sqlite-vec KNN for vector recall while preserving full post-filter result limits in multi-model indexes. Fixes #69666. (#69680) Thanks @aalekh-sarvam. |
44 | 45 | - Providers/OpenAI Codex: stop stale per-agent `openai-codex:default` OAuth profiles from shadowing a newer main-agent identity-scoped profile, and let `openclaw doctor` offer the matching cleanup. (#70393) Thanks @pashpashpash. |
45 | 46 | - ACPX: route OpenClaw ACP bridge commands through the MCP-free runtime path even when the command is wrapped with `env`, has bridge flags, or is resumed from persisted session state, so documented `acpx openclaw` setups no longer fail on per-session MCP injection. (#68741) Thanks @alexlomt. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -46,6 +46,10 @@
|
46 | 46 | "type": "number", |
47 | 47 | "minimum": 0 |
48 | 48 | }, |
| 49 | +"probeAgent": { |
| 50 | +"type": "string", |
| 51 | +"minLength": 1 |
| 52 | + }, |
49 | 53 | "mcpServers": { |
50 | 54 | "type": "object", |
51 | 55 | "additionalProperties": { |
|
132 | 136 | "help": "Reserved compatibility field for the older embedded ACPX queue-owner path. Accepted for compatibility and logged as ignored.", |
133 | 137 | "advanced": true |
134 | 138 | }, |
| 139 | +"probeAgent": { |
| 140 | +"label": "Probe Agent", |
| 141 | +"help": "Agent id used for the embedded ACP runtime health probe. Defaults to the runtime built-in probe agent (codex). Set this to another configured ACP agent id (for example `opencode` or `claude`) when the default probe agent is not installed or not authenticated, so the whole embedded ACP backend does not get marked unavailable.", |
| 142 | +"advanced": true |
| 143 | + }, |
135 | 144 | "mcpServers": { |
136 | 145 | "label": "MCP Servers", |
137 | 146 | "help": "Named MCP server definitions to inject into embedded ACP session bootstrap. Each entry needs a command and can include args and env.", |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -34,6 +34,7 @@ export type AcpxPluginConfig = {
|
34 | 34 | strictWindowsCmdWrapper?: boolean; |
35 | 35 | timeoutSeconds?: number; |
36 | 36 | queueOwnerTtlSeconds?: number; |
| 37 | +probeAgent?: string; |
37 | 38 | mcpServers?: Record<string, McpServerConfig>; |
38 | 39 | agents?: Record<string, { command: string }>; |
39 | 40 | }; |
@@ -49,6 +50,7 @@ export type ResolvedAcpxPluginConfig = {
|
49 | 50 | strictWindowsCmdWrapper: boolean; |
50 | 51 | timeoutSeconds?: number; |
51 | 52 | queueOwnerTtlSeconds: number; |
| 53 | +probeAgent?: string; |
52 | 54 | legacyCompatibilityConfig: { |
53 | 55 | strictWindowsCmdWrapper?: boolean; |
54 | 56 | queueOwnerTtlSeconds?: number; |
@@ -107,6 +109,7 @@ export const AcpxPluginConfigSchema = z.strictObject({
|
107 | 109 | .number({ error: "queueOwnerTtlSeconds must be a number >= 0" }) |
108 | 110 | .min(0, { error: "queueOwnerTtlSeconds must be a number >= 0" }) |
109 | 111 | .optional(), |
| 112 | +probeAgent: nonEmptyTrimmedString("probeAgent must be a non-empty string").optional(), |
110 | 113 | mcpServers: z.record(z.string(), McpServerConfigSchema).optional(), |
111 | 114 | agents: z |
112 | 115 | .record( |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -58,6 +58,37 @@ describe("embedded acpx plugin config", () => {
|
58 | 58 | }); |
59 | 59 | }); |
60 | 60 | |
| 61 | +it("leaves probeAgent undefined by default so the runtime picks its built-in probe agent", () => { |
| 62 | +const resolved = resolveAcpxPluginConfig({ |
| 63 | +rawConfig: undefined, |
| 64 | +workspaceDir: "/tmp/openclaw-acpx", |
| 65 | +}); |
| 66 | + |
| 67 | +expect(resolved.probeAgent).toBeUndefined(); |
| 68 | +}); |
| 69 | + |
| 70 | +it("carries an explicit probeAgent through to the resolved plugin config, trimmed", () => { |
| 71 | +const resolved = resolveAcpxPluginConfig({ |
| 72 | +rawConfig: { |
| 73 | +probeAgent: " opencode ", |
| 74 | +}, |
| 75 | +workspaceDir: "/tmp/openclaw-acpx", |
| 76 | +}); |
| 77 | + |
| 78 | +expect(resolved.probeAgent).toBe("opencode"); |
| 79 | +}); |
| 80 | + |
| 81 | +it("rejects an empty probeAgent string", () => { |
| 82 | +expect(() => |
| 83 | +resolveAcpxPluginConfig({ |
| 84 | +rawConfig: { |
| 85 | +probeAgent: "", |
| 86 | +}, |
| 87 | +workspaceDir: "/tmp/openclaw-acpx", |
| 88 | +}), |
| 89 | +).toThrow(/probeAgent must be a non-empty string/); |
| 90 | +}); |
| 91 | + |
61 | 92 | it("injects the built-in plugin-tools MCP server only when explicitly enabled", () => { |
62 | 93 | const resolved = resolveAcpxPluginConfig({ |
63 | 94 | rawConfig: { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -260,6 +260,8 @@ export function resolveAcpxPluginConfig(params: {
|
260 | 260 | ]), |
261 | 261 | ); |
262 | 262 | |
| 263 | +const probeAgent = normalized.probeAgent?.trim(); |
| 264 | + |
263 | 265 | return { |
264 | 266 | cwd, |
265 | 267 | stateDir, |
@@ -273,6 +275,7 @@ export function resolveAcpxPluginConfig(params: {
|
273 | 275 | normalized.strictWindowsCmdWrapper ?? DEFAULT_STRICT_WINDOWS_CMD_WRAPPER, |
274 | 276 | timeoutSeconds: normalized.timeoutSeconds ?? DEFAULT_ACPX_TIMEOUT_SECONDS, |
275 | 277 | queueOwnerTtlSeconds: normalized.queueOwnerTtlSeconds ?? DEFAULT_QUEUE_OWNER_TTL_SECONDS, |
| 278 | +probeAgent: probeAgent && probeAgent.length > 0 ? probeAgent : undefined, |
276 | 279 | legacyCompatibilityConfig: { |
277 | 280 | strictWindowsCmdWrapper: normalized.strictWindowsCmdWrapper, |
278 | 281 | queueOwnerTtlSeconds: normalized.queueOwnerTtlSeconds, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -145,6 +145,37 @@ describe("createAcpxRuntimeService", () => {
|
145 | 145 | await service.stop?.(ctx); |
146 | 146 | }); |
147 | 147 | |
| 148 | +it("forwards a configured probeAgent to the runtime factory so the probe does not hardcode the default", async () => { |
| 149 | +const workspaceDir = await makeTempDir(); |
| 150 | +const ctx = createServiceContext(workspaceDir); |
| 151 | +const runtime = { |
| 152 | +ensureSession: vi.fn(), |
| 153 | +runTurn: vi.fn(), |
| 154 | +cancel: vi.fn(), |
| 155 | +close: vi.fn(), |
| 156 | +probeAvailability: vi.fn(async () => {}), |
| 157 | +isHealthy: vi.fn(() => true), |
| 158 | +doctor: vi.fn(async () => ({ ok: true, message: "ok" })), |
| 159 | +}; |
| 160 | +const runtimeFactory = vi.fn(() => runtime as never); |
| 161 | +const service = createAcpxRuntimeService({ |
| 162 | +pluginConfig: { probeAgent: "opencode" }, |
| 163 | + runtimeFactory, |
| 164 | +}); |
| 165 | + |
| 166 | +await service.start(ctx); |
| 167 | + |
| 168 | +expect(runtimeFactory).toHaveBeenCalledWith( |
| 169 | +expect.objectContaining({ |
| 170 | +pluginConfig: expect.objectContaining({ |
| 171 | +probeAgent: "opencode", |
| 172 | +}), |
| 173 | +}), |
| 174 | +); |
| 175 | + |
| 176 | +await service.stop?.(ctx); |
| 177 | +}); |
| 178 | + |
148 | 179 | it("warns when legacy compatibility config is explicitly ignored", async () => { |
149 | 180 | const workspaceDir = await makeTempDir(); |
150 | 181 | const ctx = createServiceContext(workspaceDir); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -53,6 +53,7 @@ function createDefaultRuntime(params: AcpxRuntimeFactoryParams): AcpxRuntimeLike
|
53 | 53 | mcpServers: toAcpMcpServers(params.pluginConfig.mcpServers), |
54 | 54 | permissionMode: params.pluginConfig.permissionMode, |
55 | 55 | nonInteractivePermissions: params.pluginConfig.nonInteractivePermissions, |
| 56 | +probeAgent: params.pluginConfig.probeAgent, |
56 | 57 | timeoutMs: |
57 | 58 | params.pluginConfig.timeoutSeconds != null |
58 | 59 | ? params.pluginConfig.timeoutSeconds * 1_000 |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。