Skip to content

Commit c138368

Browse files
authored
feat: add Codex harness extension seams
Co-authored-by: Eva <100yenadmin@users.noreply.github.com>
1 parent d85dc46 commit c138368

38 files changed

Lines changed: 1173 additions & 49 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
1919
- Agents/tools: add optional per-call `timeoutMs` support for image, video, music, and TTS generation tools so agents can extend provider request timeouts only when a specific generation needs it.
2020
- Agents/subagents: add optional forked context for native `sessions_spawn` runs so agents can let a child inherit the requester transcript when needed, while keeping clean isolated sessions as the default; includes prompt guidance, context-engine hook metadata, docs, and QA coverage.
2121
- Codex harness: add structured debug logging for embedded harness selection decisions so `/status` stays simple while gateway logs explain auto-selection and Pi fallback reasons. (#70760) Thanks @100yenadmin.
22+
- Plugin SDK/Codex harness: add provider-owned transport/auth/follow-up seams and harness result classification so Codex-style runtimes can participate in fallback policy without core special-casing. (#70772) Thanks @100yenadmin.
2223
- Dependencies/Pi: update bundled Pi packages to `0.70.0`, use Pi's upstream `gpt-5.5` catalog metadata for OpenAI and OpenAI Codex, and keep only local `gpt-5.5-pro` forward-compat handling.
2324
- Models/CLI: speed up `openclaw models list --all --provider <id>` for bundled providers with safe static catalogs while keeping live and third-party providers on registry discovery. (#70632) Thanks @shakkernerd.
2425
- Models/CLI: avoid broad registry enumeration for default `openclaw models list`, reducing default listing latency while preserving configured-row output. (#70883) Thanks @shakkernerd.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
c57d43f93ec2930b099dd5c5777f201f1bdd1ab432eeb4049b6e62ff23fe8112 plugin-sdk-api-baseline.json
2-
ece1ea689914c4070b587551e86c6bed6598feba90457ab489222e168b2d9298 plugin-sdk-api-baseline.jsonl
1+
8ca22ea6125fb198641c676d73b4df5a3bc49079be68bef8ed0718a54c1bb53a plugin-sdk-api-baseline.json
2+
197d9743128020062fc457228fa9139d0bd465d9e1775101bfc39137f4a10896 plugin-sdk-api-baseline.jsonl

docs/tools/capability-cookbook.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,25 @@ Feature/channel plugin:
6969
- calls `api.runtime.*` or the matching `plugin-sdk/*-runtime` helper
7070
- never calls a vendor implementation directly
7171

72+
## Provider and Harness Seams
73+
74+
Use provider hooks when the behavior belongs to the model provider contract
75+
rather than the generic agent loop. Examples include provider-specific request
76+
params after transport selection, auth-profile preference, prompt overlays, and
77+
follow-up fallback routing after model/profile failover.
78+
79+
Use agent harness hooks when the behavior belongs to the runtime that is
80+
executing a turn. Harnesses can classify successful-but-unusable attempt results
81+
such as empty, reasoning-only, or planning-only responses so the outer model
82+
fallback policy can make the retry decision.
83+
84+
Keep both seams narrow:
85+
86+
- core owns the retry/fallback policy
87+
- provider plugins own provider-specific request/auth/routing hints
88+
- harness plugins own runtime-specific attempt classification
89+
- third-party plugins return hints, not direct mutations of core state
90+
7291
## File checklist
7392

7493
For a new capability, expect to touch these areas:

docs/tools/plugin.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ sidebarTitle: "Install and Configure"
99
---
1010

1111
Plugins extend OpenClaw with new capabilities: channels, model providers,
12-
tools, skills, speech, realtime transcription, realtime voice,
13-
media-understanding, image generation, video generation, web fetch, web
12+
agent harnesses, tools, skills, speech, realtime transcription, realtime
13+
voice, media-understanding, image generation, video generation, web fetch, web
1414
search, and more. Some plugins are **core** (shipped with OpenClaw), others
1515
are **external** (published on npm by the community).
1616

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ export async function runCodexAppServerAttempt(
448448
sessionId: params.sessionId,
449449
provider: params.provider,
450450
model: params.modelId,
451+
resolvedRef: `${params.provider}/${params.modelId}`,
451452
assistantTexts: [],
452453
},
453454
ctx: hookContext,
@@ -602,6 +603,7 @@ export async function runCodexAppServerAttempt(
602603
sessionId: params.sessionId,
603604
provider: params.provider,
604605
model: params.modelId,
606+
resolvedRef: `${params.provider}/${params.modelId}`,
605607
assistantTexts: result.assistantTexts,
606608
...(result.lastAssistant ? { lastAssistant: result.lastAssistant } : {}),
607609
...(result.attemptUsage ? { usage: result.attemptUsage } : {}),

extensions/telegram/src/bot.create-telegram-bot.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ describe("createTelegramBot", () => {
240240
it("lets /status bypass a busy Telegram topic lane", async () => {
241241
installPerKeySequentializer();
242242
loadConfig.mockReturnValue({
243-
commands: { native: true },
244243
channels: {
245244
telegram: {
246245
dmPolicy: "open",

src/agents/cli-runner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export async function runPreparedCliAgent(
199199
sessionId: params.sessionId,
200200
provider: params.provider,
201201
model: context.modelId,
202+
resolvedRef: `${params.provider}/${context.modelId}`,
202203
assistantTexts,
203204
...(lastAssistant ? { lastAssistant } : {}),
204205
...(output.usage ? { usage: output.usage } : {}),

src/agents/embedded-runner.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export {
2+
abortEmbeddedAgentRun,
3+
compactEmbeddedAgentSession,
4+
isEmbeddedAgentRunActive,
5+
isEmbeddedAgentRunStreaming,
6+
queueEmbeddedAgentMessage,
7+
resolveActiveEmbeddedAgentRunSessionId,
8+
resolveEmbeddedSessionLane,
9+
runEmbeddedAgent,
10+
waitForEmbeddedAgentRunEnd,
11+
} from "./pi-embedded-runner.js";
12+
export type {
13+
EmbeddedAgentCompactResult,
14+
EmbeddedAgentMeta,
15+
EmbeddedAgentRunMeta,
16+
EmbeddedAgentRunResult,
17+
} from "./pi-embedded-runner.js";

src/agents/harness/selection.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,34 @@ describe("runAgentHarnessAttemptWithFallback", () => {
138138
expect(piRunAttempt).not.toHaveBeenCalled();
139139
});
140140

141+
it("annotates non-ok harness result classifications for outer model fallback", async () => {
142+
process.env.OPENCLAW_AGENT_RUNTIME = "auto";
143+
const classify = vi.fn(() => "empty" as const);
144+
registerAgentHarness(
145+
{
146+
id: "codex",
147+
label: "Classifying Codex",
148+
supports: (ctx) =>
149+
ctx.provider === "codex" ? { supported: true, priority: 100 } : { supported: false },
150+
runAttempt: vi.fn(async () => createAttemptResult("codex")),
151+
classify,
152+
},
153+
{ ownerPluginId: "codex" },
154+
);
155+
156+
const params = createAttemptParams();
157+
const result = await runAgentHarnessAttemptWithFallback(params);
158+
159+
expect(classify).toHaveBeenCalledWith(
160+
expect.objectContaining({ sessionIdUsed: "codex" }),
161+
params,
162+
);
163+
expect(result).toMatchObject({
164+
agentHarnessId: "codex",
165+
agentHarnessResultClassification: "empty",
166+
});
167+
});
168+
141169
it("honors env fallback override over config fallback", async () => {
142170
process.env.OPENCLAW_AGENT_RUNTIME = "auto";
143171
process.env.OPENCLAW_AGENT_HARNESS_FALLBACK = "none";

src/agents/harness/selection.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,12 @@ export async function runAgentHarnessAttemptWithFallback(
189189
});
190190
if (harness.id === "pi") {
191191
const result = await harness.runAttempt(params);
192-
return { ...result, agentHarnessId: harness.id };
192+
return applyHarnessResultClassification(harness, result, params);
193193
}
194194

195195
try {
196196
const result = await harness.runAttempt(params);
197-
return { ...result, agentHarnessId: harness.id };
197+
return applyHarnessResultClassification(harness, result, params);
198198
} catch (error) {
199199
log.warn(`${harness.label} failed; not falling back to embedded PI backend`, {
200200
harnessId: harness.id,
@@ -263,6 +263,22 @@ function logAgentHarnessSelection(
263263
});
264264
}
265265

266+
function applyHarnessResultClassification(
267+
harness: AgentHarness,
268+
result: EmbeddedRunAttemptResult,
269+
params: EmbeddedRunAttemptParams,
270+
): EmbeddedRunAttemptResult {
271+
const classification = harness.classify?.(result, params);
272+
if (!classification || classification === "ok") {
273+
return { ...result, agentHarnessId: harness.id };
274+
}
275+
return {
276+
...result,
277+
agentHarnessId: harness.id,
278+
agentHarnessResultClassification: classification,
279+
};
280+
}
281+
266282
function resolvePinnedAgentHarnessPolicy(
267283
agentHarnessId: string | undefined,
268284
): AgentHarnessPolicy | undefined {

0 commit comments

Comments
 (0)