Skip to content

Commit 0f215f5

Browse files
authored
Merge branch 'main' into user/giodl/provider-timeout-overlay
2 parents e1a0cdc + 8284c03 commit 0f215f5

13 files changed

Lines changed: 434 additions & 28 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Docs: https://docs.openclaw.ai
1212

1313
- Media/audio: skip empty structured sherpa-onnx transcripts instead of treating the raw JSON payload as spoken text. (#84667) Thanks @TurboTheTurtle.
1414
- Memory-core/dreaming: reuse stable narrative subagent session keys per workspace and phase while keeping per-run idempotency and bounded cleanup, so stale `dreaming-narrative-*` sessions do not accumulate. Fixes #68252, #69187, and #70402. (#70464) Thanks @chiyouYCH.
15+
- Trajectory/support: tolerate partial skill snapshot entries when building support metadata so rejected skill path scans no longer abort trajectory capture. (#71185) Thanks @lukeboyett.
16+
- Agents/Pi: disable the embedded pi-coding-agent runtime auto-retry so OpenClaw's own retry and failover loop does not replay failed tool calls through a nested SDK retry. Fixes #73781. (#74434) Thanks @yelog.
1517
- CLI/perf: keep `setup --help`, `onboard --help`, and `configure --help` out of the full wizard runtime while preserving the existing help output. (#84488) Thanks @frankekn.
1618
- CLI/perf: keep `agents --help` out of agents action/runtime imports so help, completion, and command discovery paths avoid loading the full agents runtime. (#84483) Thanks @frankekn.
1719

@@ -748,6 +750,7 @@ Docs: https://docs.openclaw.ai
748750
- Require canonical node platform IDs [AI]. (#81880) Thanks @pgondhi987.
749751
- Agents/Azure OpenAI Responses: default unset Azure OpenAI API versions to `preview` so `/openai/v1/responses` calls use Azure's current Responses API route. (#82026) Thanks @leoge007.
750752
- Control UI/WebChat: compact the desktop chat header controls into a single aligned row so the session, model, thinking, and action controls no longer waste vertical space. Thanks @BunsDev.
753+
- Control UI/settings: widen the Personal quick-settings card to a 3/1 desktop split and keep Appearance/Automations below it on narrower layouts. Thanks @BunsDev.
751754
- Agents/model catalog: reuse manifest model-id normalization metadata while loading persisted read-only catalog rows, avoiding repeated metadata scans.
752755
- Agents: retry empty final turns for generic `anthropic-messages` providers instead of limiting non-visible recovery to Kimi, so custom/proxied Anthropic-compatible routes can recover with a visible answer. Addresses #46080. Thanks @wmgx, @w1tv, and @iFwu.
753756
- Agents/replies: strip workflow `<function_response>` scaffolding from user-visible sanitizer paths so raw tool output does not leak into chat history, transcript mirrors, or channel replies. Fixes #47444. Thanks @5toCode.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1+
import type { DoctorSessionRouteStateOwner } from "openclaw/plugin-sdk/runtime-doctor";
2+
13
export const legacyConfigRules = [];
4+
5+
export const sessionRouteStateOwners: DoctorSessionRouteStateOwner[] = [
6+
{
7+
id: "anthropic",
8+
label: "Anthropic",
9+
providerIds: ["anthropic", "claude-cli"],
10+
runtimeIds: ["claude-cli"],
11+
cliSessionKeys: ["claude-cli"],
12+
authProfilePrefixes: ["anthropic:", "claude-cli:"],
13+
},
14+
];
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { DoctorSessionRouteStateOwner } from "openclaw/plugin-sdk/runtime-doctor";
2+
3+
export const sessionRouteStateOwners: DoctorSessionRouteStateOwner[] = [
4+
{
5+
id: "google",
6+
label: "Google",
7+
providerIds: ["google", "google-antigravity", "google-gemini-cli", "google-vertex"],
8+
runtimeIds: ["google-gemini-cli"],
9+
cliSessionKeys: ["google-gemini-cli", "gemini-cli"],
10+
authProfilePrefixes: [
11+
"google:",
12+
"google-antigravity:",
13+
"google-gemini-cli:",
14+
"google-vertex:",
15+
"gemini-cli:",
16+
],
17+
},
18+
];

src/agents/pi-project-settings.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,8 @@ describe("createPreparedEmbeddedPiSettingsManager", () => {
161161
});
162162

163163
expect(settingsManager.getShellCommandPrefix()).toBe("echo trusted &&");
164-
expect(settingsManager.getRetryEnabled()).toBe(true);
164+
expect(settingsManager.getRetryEnabled()).toBe(false);
165165

166-
settingsManager.setRetryEnabled(false);
167166
await settingsManager.flush();
168167

169168
const diskSettings = JSON.parse(await fs.readFile(agentSettingsPath, "utf8")) as {

src/agents/pi-project-settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,10 @@ export function createPreparedEmbeddedPiSettingsManager(params: {
6161
cfg: params.cfg,
6262
contextTokenBudget: params.contextTokenBudget,
6363
});
64+
// Disable the pi-coding-agent auto-retry. OpenClaw has its own comprehensive
65+
// retry layer (failover rotation, auth profile rotation, empty-error retry,
66+
// thinking-level fallback) in run.ts. Having both layers active creates a
67+
// double-retry that can replay failed tool calls in an unbounded loop (#73781).
68+
settingsManager.setRetryEnabled(false);
6469
return settingsManager;
6570
}

src/commands/doctor-session-state-providers.test.ts

Lines changed: 199 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ describe("doctor session state provider routes", () => {
4343
},
4444
}),
4545
).toBe(true);
46+
47+
expect(
48+
storeMayContainPluginSessionRouteState({
49+
"agent:main:telegram:direct:2": {
50+
sessionId: "session-claude-cli",
51+
updatedAt: 1,
52+
agentRuntimeOverride: "claude-cli",
53+
},
54+
}),
55+
).toBe(true);
4656
});
4757

4858
it("preserves configured provider CLI runtimes before harness policy normalization", () => {
@@ -135,10 +145,11 @@ describe("doctor session state provider routes", () => {
135145
ownerId: "codex",
136146
ownerLabel: "Codex",
137147
cliSessionKeys: ["codex-cli"],
148+
pinnedRuntimeKeys: ["agentHarnessId"],
138149
reasons: [
139150
"auto model override",
140-
"runtime model state",
141151
"pinned runtime",
152+
"runtime model state",
142153
"CLI session binding",
143154
"auto auth profile override",
144155
],
@@ -206,7 +217,7 @@ describe("doctor session state provider routes", () => {
206217
]);
207218
});
208219

209-
it("keeps owner state when owner remains in the configured route", () => {
220+
it("clears stale runtime pins while preserving configured owner model state", () => {
210221
const sessionKey = "agent:main:telegram:direct:3";
211222
const entry: Record<string, unknown> = {
212223
sessionId: "sess-configured-codex",
@@ -234,7 +245,28 @@ describe("doctor session state provider routes", () => {
234245
},
235246
});
236247

237-
expect(scan).toEqual({ repairs: [], manualReview: [] });
248+
expect(scan.manualReview).toStrictEqual([]);
249+
expect(scan.repairs).toEqual([
250+
{
251+
key: sessionKey,
252+
ownerId: "codex",
253+
ownerLabel: "Codex",
254+
cliSessionKeys: ["codex-cli"],
255+
pinnedRuntimeKeys: ["agentHarnessId"],
256+
reasons: ["pinned runtime"],
257+
},
258+
]);
259+
260+
expect(applySessionRouteStateRepair({ entry, repair: scan.repairs[0], now: 123 })).toBe(true);
261+
expect(entry.updatedAt).toBe(123);
262+
expect(entry.providerOverride).toBe("openai-codex");
263+
expect(entry.modelOverride).toBe("gpt-5.4");
264+
expect(entry.modelProvider).toBe("openai-codex");
265+
expect(entry.model).toBe("gpt-5.4");
266+
expect(entry.agentHarnessId).toBeUndefined();
267+
expect(entry.cliSessionBindings).toStrictEqual({
268+
"codex-cli": { sessionId: "codex-session-3" },
269+
});
238270
});
239271

240272
it("keeps owner CLI state when owner runtime is still configured", () => {
@@ -263,4 +295,168 @@ describe("doctor session state provider routes", () => {
263295

264296
expect(scan).toEqual({ repairs: [], manualReview: [] });
265297
});
298+
299+
it("clears stale agentRuntimeOverride-only pins when current route no longer uses the owner", () => {
300+
const sessionKey = "agent:main:telegram:direct:5";
301+
const entry: Record<string, unknown> = {
302+
sessionId: "sess-stale-claude-cli",
303+
updatedAt: 1,
304+
agentRuntimeOverride: "claude-cli",
305+
};
306+
307+
const scan = scanSessionRouteStateOwners({
308+
owners: [
309+
{
310+
id: "anthropic",
311+
label: "Anthropic",
312+
providerIds: ["anthropic"],
313+
runtimeIds: ["claude-cli"],
314+
cliSessionKeys: ["claude-cli"],
315+
authProfilePrefixes: ["anthropic:", "claude-cli:"],
316+
},
317+
],
318+
store: { [sessionKey]: entry },
319+
routes: {
320+
[sessionKey]: {
321+
defaultProvider: "openai",
322+
configuredModelRefs: ["openai/gpt-5.5"],
323+
runtime: "pi",
324+
},
325+
},
326+
});
327+
328+
expect(scan.manualReview).toStrictEqual([]);
329+
expect(scan.repairs).toEqual([
330+
{
331+
key: sessionKey,
332+
ownerId: "anthropic",
333+
ownerLabel: "Anthropic",
334+
cliSessionKeys: ["claude-cli"],
335+
pinnedRuntimeKeys: ["agentRuntimeOverride"],
336+
reasons: ["pinned runtime"],
337+
},
338+
]);
339+
340+
expect(applySessionRouteStateRepair({ entry, repair: scan.repairs[0], now: 123 })).toBe(true);
341+
expect(entry.sessionId).toBe("sess-stale-claude-cli");
342+
expect(entry.updatedAt).toBe(123);
343+
expect(entry.agentRuntimeOverride).toBeUndefined();
344+
});
345+
346+
it("keeps agentRuntimeOverride pins when owner runtime remains configured", () => {
347+
const sessionKey = "agent:main:telegram:direct:6";
348+
const entry: Record<string, unknown> = {
349+
sessionId: "sess-active-claude-cli",
350+
updatedAt: 1,
351+
agentRuntimeOverride: "claude-cli",
352+
};
353+
354+
const scan = scanSessionRouteStateOwners({
355+
owners: [
356+
{
357+
id: "anthropic",
358+
label: "Anthropic",
359+
providerIds: ["anthropic"],
360+
runtimeIds: ["claude-cli"],
361+
cliSessionKeys: ["claude-cli"],
362+
authProfilePrefixes: ["anthropic:", "claude-cli:"],
363+
},
364+
],
365+
store: { [sessionKey]: entry },
366+
routes: {
367+
[sessionKey]: {
368+
defaultProvider: "anthropic",
369+
configuredModelRefs: ["anthropic/claude-opus-4.7"],
370+
runtime: "claude-cli",
371+
},
372+
},
373+
});
374+
375+
expect(scan).toEqual({ repairs: [], manualReview: [] });
376+
});
377+
378+
it("clears stale owner runtime pins when owner provider remains configured", () => {
379+
const sessionKey = "agent:main:telegram:direct:7";
380+
const entry: Record<string, unknown> = {
381+
sessionId: "sess-provider-active-runtime-stale",
382+
updatedAt: 1,
383+
agentRuntimeOverride: "claude-cli",
384+
};
385+
386+
const scan = scanSessionRouteStateOwners({
387+
owners: [
388+
{
389+
id: "anthropic",
390+
label: "Anthropic",
391+
providerIds: ["anthropic"],
392+
runtimeIds: ["claude-cli"],
393+
cliSessionKeys: ["claude-cli"],
394+
authProfilePrefixes: ["anthropic:", "claude-cli:"],
395+
},
396+
],
397+
store: { [sessionKey]: entry },
398+
routes: {
399+
[sessionKey]: {
400+
defaultProvider: "anthropic",
401+
configuredModelRefs: ["anthropic/claude-opus-4.7"],
402+
runtime: "pi",
403+
},
404+
},
405+
});
406+
407+
expect(scan.manualReview).toStrictEqual([]);
408+
expect(scan.repairs).toEqual([
409+
{
410+
key: sessionKey,
411+
ownerId: "anthropic",
412+
ownerLabel: "Anthropic",
413+
cliSessionKeys: ["claude-cli"],
414+
pinnedRuntimeKeys: ["agentRuntimeOverride"],
415+
reasons: ["pinned runtime"],
416+
},
417+
]);
418+
419+
expect(applySessionRouteStateRepair({ entry, repair: scan.repairs[0], now: 123 })).toBe(true);
420+
expect(entry.updatedAt).toBe(123);
421+
expect(entry.agentRuntimeOverride).toBeUndefined();
422+
});
423+
424+
it("preserves non-owner runtime overrides when clearing owner harness pins", () => {
425+
const sessionKey = "agent:main:telegram:direct:8";
426+
const entry: Record<string, unknown> = {
427+
sessionId: "sess-mixed-runtime-pins",
428+
updatedAt: 1,
429+
agentHarnessId: "codex-cli",
430+
agentRuntimeOverride: "claude-cli",
431+
};
432+
433+
const scan = scanSessionRouteStateOwners({
434+
owners: [codexOwner],
435+
store: { [sessionKey]: entry },
436+
routes: {
437+
[sessionKey]: {
438+
defaultProvider: "openai",
439+
configuredModelRefs: ["openai/gpt-5.5"],
440+
runtime: "pi",
441+
},
442+
},
443+
});
444+
445+
expect(scan.manualReview).toStrictEqual([]);
446+
expect(scan.repairs).toEqual([
447+
{
448+
key: sessionKey,
449+
ownerId: "codex",
450+
ownerLabel: "Codex",
451+
cliSessionKeys: ["codex-cli"],
452+
pinnedRuntimeKeys: ["agentHarnessId"],
453+
reasons: ["pinned runtime"],
454+
},
455+
]);
456+
457+
expect(applySessionRouteStateRepair({ entry, repair: scan.repairs[0], now: 123 })).toBe(true);
458+
expect(entry.updatedAt).toBe(123);
459+
expect(entry.agentHarnessId).toBeUndefined();
460+
expect(entry.agentRuntimeOverride).toBe("claude-cli");
461+
});
266462
});

src/commands/doctor-session-state-providers.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ function entryMayContainPluginSessionRouteState(entry: SessionEntry): boolean {
114114
normalizeString(record.modelProvider) !== undefined ||
115115
normalizeString(record.model) !== undefined ||
116116
normalizeString(record.agentHarnessId) !== undefined ||
117+
normalizeString(record.agentRuntimeOverride) !== undefined ||
117118
record.cliSessionBindings !== undefined ||
118119
record.cliSessionIds !== undefined ||
119120
normalizeString(record.authProfileOverride) !== undefined ||
@@ -138,6 +139,7 @@ export type DoctorSessionRouteStateRepair = {
138139
ownerId: string;
139140
ownerLabel: string;
140141
reasons: string[];
142+
pinnedRuntimeKeys: string[];
141143
cliSessionKeys: string[];
142144
};
143145

@@ -232,7 +234,11 @@ function scanEntryForOwner(params: {
232234
const cliSessionKeys = [...normalizeIdSet(params.owner.cliSessionKeys)];
233235
const authProfilePrefixes = normalizePrefixList(params.owner.authProfilePrefixes);
234236
const routeAllowsOwner = routeAllowsOwnerState({ owner: params.owner, route: params.route });
237+
const routeRuntime = normalizeString(params.route?.runtime);
238+
const routeAllowsOwnerRuntime =
239+
routeRuntime !== undefined && runtimeIds.has(normalizeProviderId(routeRuntime));
235240
const reasons: string[] = [];
241+
const pinnedRuntimeKeys: string[] = [];
236242
const directOverride = resolvePersistedOverrideModelRef({
237243
defaultProvider: params.route?.defaultProvider ?? "",
238244
overrideProvider: params.entry.providerOverride,
@@ -274,6 +280,18 @@ function scanEntryForOwner(params: {
274280

275281
const explicitOwnedOverride =
276282
directOverrideIsOwned && directOverrideSource !== undefined && directOverrideSource !== "auto";
283+
if (!routeAllowsOwnerRuntime && !explicitOwnedOverride) {
284+
const harnessId = normalizeString(params.entry.agentHarnessId);
285+
if (harnessId && runtimeIds.has(normalizeProviderId(harnessId))) {
286+
addReason(reasons, "pinned runtime");
287+
pinnedRuntimeKeys.push("agentHarnessId");
288+
}
289+
const runtimeOverride = normalizeString(params.entry.agentRuntimeOverride);
290+
if (runtimeOverride && runtimeIds.has(normalizeProviderId(runtimeOverride))) {
291+
addReason(reasons, "pinned runtime");
292+
pinnedRuntimeKeys.push("agentRuntimeOverride");
293+
}
294+
}
277295
if (!routeAllowsOwner && !explicitOwnedOverride) {
278296
const runtimeModel = normalizeString(params.entry.model);
279297
const runtimeRef = runtimeModel
@@ -284,10 +302,6 @@ function scanEntryForOwner(params: {
284302
if (runtimeRef && providerIds.has(normalizeProviderId(runtimeRef.provider))) {
285303
addReason(reasons, "runtime model state");
286304
}
287-
const harnessId = normalizeString(params.entry.agentHarnessId);
288-
if (harnessId && runtimeIds.has(normalizeProviderId(harnessId))) {
289-
addReason(reasons, "pinned runtime");
290-
}
291305
if (hasOwnedCliSession({ entry: params.entry, cliSessionKeys })) {
292306
addReason(reasons, "CLI session binding");
293307
}
@@ -308,6 +322,7 @@ function scanEntryForOwner(params: {
308322
ownerId: params.owner.id,
309323
ownerLabel: params.owner.label,
310324
reasons,
325+
pinnedRuntimeKeys,
311326
cliSessionKeys,
312327
},
313328
};
@@ -396,7 +411,9 @@ export function applySessionRouteStateRepair(params: {
396411
clear("fallbackNoticeReason");
397412
}
398413
if (params.repair.reasons.includes("pinned runtime")) {
399-
clear("agentHarnessId");
414+
for (const key of params.repair.pinnedRuntimeKeys) {
415+
clear(key);
416+
}
400417
}
401418
if (params.repair.reasons.includes("CLI session binding")) {
402419
changed =

0 commit comments

Comments
 (0)