Skip to content

Commit 0314819

Browse files
fix(agents): replace prose terminal classifiers (#93228)
* fix(agents): replace prose terminal classifiers Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * fix(agents): preserve terminal failure lifecycles * fix(agents): order parallel terminal summaries * fix(agents): preserve structured post-tool silence * fix(agents): preserve structured replay provenance --------- Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
1 parent eab22a9 commit 0314819

88 files changed

Lines changed: 5156 additions & 2121 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/plugins/adding-capabilities.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ If the work is vendor-only and no shared contract exists yet, stop and define th
7171

7272
Use **provider hooks** when the behavior belongs to the model provider contract rather than the generic agent loop. Examples include provider-specific request params after transport selection, auth-profile preference, prompt overlays, and follow-up fallback routing after model/profile failover.
7373

74-
Use **agent harness hooks** when the behavior belongs to the runtime that is executing a turn. Harnesses can classify successful-but-unusable attempt results such as empty, reasoning-only, or planning-only responses so the outer model fallback policy can make the retry decision.
74+
Use **agent harness hooks** when the behavior belongs to the runtime that is executing a turn. Harnesses can classify explicit protocol outcomes such as empty output, reasoning without visible output, or a structured plan without a final answer so the outer model fallback policy can make the retry decision.
7575

7676
Keep both seams narrow:
7777

docs/plugins/sdk-agent-harness.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,10 @@ Native harnesses that own their own protocol projection can use
180180
`openclaw/plugin-sdk/agent-harness-runtime` when a completed turn produced no
181181
visible assistant text. The helper returns `empty`, `reasoning-only`, or
182182
`planning-only` so OpenClaw's fallback policy can decide whether to retry on a
183-
different model. It intentionally leaves prompt errors, in-flight turns, and
184-
intentional silent replies such as `NO_REPLY` unclassified.
183+
different model. `planning-only` requires the harness's explicit `planText`
184+
field; OpenClaw does not infer it from assistant prose. The helper intentionally
185+
leaves prompt errors, in-flight turns, and intentional silent replies such as
186+
`NO_REPLY` unclassified.
185187

186188
### Native Codex harness mode
187189

docs/providers/openai.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,10 +1040,11 @@ the Server-side compaction accordion below.
10401040
```
10411041

10421042
With `strict-agentic`, OpenClaw:
1043-
- No longer treats a plan-only turn as successful progress when a tool action is available
1044-
- Retries the turn with an act-now steer
10451043
- Auto-enables `update_plan` for substantial work
1046-
- Surfaces an explicit blocked state if the model keeps planning without acting
1044+
- Retries structurally empty or reasoning-only turns with a visible-answer continuation
1045+
- Uses explicit harness plan events when the selected harness provides them
1046+
1047+
OpenClaw does not classify assistant prose to decide whether a turn is a plan, progress update, or final answer.
10471048

10481049
<Note>
10491050
Scoped to OpenAI and Codex GPT-5-family runs only. Other providers and older model families keep default behavior.

extensions/codex/src/app-server/dynamic-tool-build.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import os from "node:os";
44
import path from "node:path";
55
import {
66
embeddedAgentLog,
7+
isToolWrappedWithBeforeToolCallHook,
78
type EmbeddedRunAttemptParams,
9+
wrapToolWithBeforeToolCallHook,
810
} from "openclaw/plugin-sdk/agent-harness-runtime";
911
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
1012
import {
@@ -581,6 +583,46 @@ describe("Codex app-server dynamic tool build", () => {
581583
});
582584
});
583585

586+
it("forwards the tool outcome observer into Codex dynamic tools", async () => {
587+
const sessionFile = path.join(tempDir, "session.jsonl");
588+
const workspaceDir = path.join(tempDir, "workspace");
589+
const params = createParams(sessionFile, workspaceDir);
590+
const onToolOutcome = vi.fn();
591+
params.disableTools = false;
592+
params.onToolOutcome = onToolOutcome;
593+
params.runtimePlan = createCodexRuntimePlanFixture();
594+
const factoryOptions: unknown[] = [];
595+
setOpenClawCodingToolsFactoryForTests((options) => {
596+
factoryOptions.push(options);
597+
return [];
598+
});
599+
600+
await buildDynamicToolsForTest(params, workspaceDir, { sandbox: null as never });
601+
602+
expect(factoryOptions[0]).toMatchObject({ onToolOutcome });
603+
});
604+
605+
it("preserves before-tool wrapping through Codex runtime normalization", async () => {
606+
const sessionFile = path.join(tempDir, "session.jsonl");
607+
const workspaceDir = path.join(tempDir, "workspace");
608+
const params = createParams(sessionFile, workspaceDir);
609+
params.disableTools = false;
610+
const runtimePlan = createCodexRuntimePlanFixture();
611+
runtimePlan.tools.normalize = (tools) => tools.map((tool) => ({ ...tool }));
612+
params.runtimePlan = runtimePlan;
613+
const wrappedTool = wrapToolWithBeforeToolCallHook(createRuntimeDynamicTool("web_fetch"), {
614+
agentId: "main",
615+
sessionId: params.sessionId,
616+
});
617+
setOpenClawCodingToolsFactoryForTests(() => [wrappedTool]);
618+
619+
const tools = await buildDynamicToolsForTest(params, workspaceDir, { sandbox: null as never });
620+
621+
expect(tools).toHaveLength(1);
622+
expect(tools[0]).not.toBe(wrappedTool);
623+
expect(isToolWrappedWithBeforeToolCallHook(tools[0])).toBe(true);
624+
});
625+
584626
it("passes runtime config into Codex exec dynamic tool construction", async () => {
585627
const sessionFile = path.join(tempDir, "session.jsonl");
586628
const workspaceDir = path.join(tempDir, "workspace");

extensions/codex/src/app-server/dynamic-tool-build.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ export async function buildDynamicTools(input: DynamicToolBuildParams) {
290290
recordToolPrepStage: (name) => {
291291
toolBuildStages.mark(name);
292292
},
293+
onToolOutcome: params.onToolOutcome,
293294
});
294295
toolBuildStages.mark("create-openclaw-coding-tools");
295296
const preNormalizationDiagnostics: RuntimeToolSchemaDiagnostic[] = [];

0 commit comments

Comments
 (0)