Skip to content

Commit b63a688

Browse files
committed
Auto-merge upstream openclaw/openclaw
2 parents 4e55a6a + 4a16cf8 commit b63a688

19 files changed

Lines changed: 287 additions & 250 deletions

.pi/prompts/landpr.md

Lines changed: 0 additions & 73 deletions
This file was deleted.

.pi/prompts/reviewpr.md

Lines changed: 0 additions & 134 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Docs: https://docs.openclaw.ai
3737
- Discord: keep slash command follow-up chunks ephemeral when the command is configured for ephemeral replies, so long `/status` output no longer leaks fallback model or runtime details into the public channel. (#69869) thanks @gumadeiras.
3838
- Plugins/discovery: reject package plugin source entries that escape the package directory before explicit runtime entries or inferred built JavaScript peers can be used. (#69868) thanks @gumadeiras.
3939
- CLI/channels: resolve channel presence through a shared policy that keeps ambient env vars and stale persisted auth from surfacing disabled bundled plugins in status, doctor, security audit, and cron delivery validation unless the channel or plugin is effectively enabled or explicitly configured. (#69862) Thanks @gumadeiras.
40+
- Doctor/plugins: hydrate legacy partial interactive handler state before plugin reload clears dedupe caches, so `openclaw doctor` and post-update doctor runs no longer crash with `Cannot read properties of undefined (reading 'clear')`. (#70135) Thanks @ngutman.
4041
- Control UI/config: preserve intentionally empty raw config snapshots when clearing pending updates so reset restores the original bytes instead of synthesizing JSON for blank config files. (#68178) Thanks @BunsDev.
4142
- memory-core/dreaming: surface a `Dreaming status: blocked` line in `openclaw memory status` when dreaming is enabled but the heartbeat that drives the managed cron is not firing for the default agent, and add a Troubleshooting section to the dreaming docs covering the two common causes (per-agent `heartbeat` blocks excluding `main`, and `heartbeat.every` set to `0`/empty/invalid), so the silent failure described in #69843 becomes legible on the status surface.
4243
- Cron/run-log: report generic `message` tool sends under the resolved delivery channel when they match the cron target, while preserving account-specific mismatch checks for delivery traces. (#69940) Thanks @davehappyminion.
@@ -46,6 +47,8 @@ Docs: https://docs.openclaw.ai
4647
- Agents/harness: surface selected plugin harness failures directly instead of replaying the same turn through embedded PI, preventing misleading secondary PI auth errors and avoiding duplicate side effects.
4748
- OpenAI Codex: add a ChatGPT device-code auth option beside browser OAuth, so headless or callback-hostile setups can sign in without relying on the localhost browser callback. (#69557) Thanks @vincentkoc.
4849
- CLI sessions: keep provider-owned CLI sessions through implicit daily expiry while preserving explicit reset behavior, and retain Claude CLI binding metadata across gateway agent requests. (#70106) Thanks @obviyus.
50+
- fix(config): accept truncateAfterCompaction (#68395). Thanks @MonkeyLeeT
51+
- CLI/Claude: keep Claude CLI session bindings stable across OAuth access-token refreshes, so gateway restarts continue the same Claude conversation instead of minting a fresh one. (#70132) Thanks @obviyus.
4952

5053
## 2026.4.21
5154

src/agents/cli-auth-epoch.test.ts

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,50 @@ describe("resolveCliAuthEpoch", () => {
3030
).resolves.toBeUndefined();
3131
});
3232

33-
it("changes when claude cli credentials change", async () => {
33+
it("keeps claude cli oauth epochs stable across access-token refreshes", async () => {
3434
let access = "access-a";
35+
let expires = 1;
3536
setCliAuthEpochTestDeps({
3637
readClaudeCliCredentialsCached: () => ({
3738
type: "oauth",
3839
provider: "anthropic",
3940
access,
4041
refresh: "refresh",
41-
expires: 1,
42+
expires,
4243
}),
4344
});
4445

4546
const first = await resolveCliAuthEpoch({ provider: "claude-cli" });
4647
access = "access-b";
48+
expires = 2;
49+
const second = await resolveCliAuthEpoch({ provider: "claude-cli" });
50+
51+
expect(first).toBeDefined();
52+
expect(second).toBe(first);
53+
});
54+
55+
it("changes claude cli oauth epochs when the refresh token changes", async () => {
56+
let refresh = "refresh-a";
57+
setCliAuthEpochTestDeps({
58+
readClaudeCliCredentialsCached: () => ({
59+
type: "oauth",
60+
provider: "anthropic",
61+
access: "access",
62+
refresh,
63+
expires: 1,
64+
}),
65+
});
66+
67+
const first = await resolveCliAuthEpoch({ provider: "claude-cli" });
68+
refresh = "refresh-b";
4769
const second = await resolveCliAuthEpoch({ provider: "claude-cli" });
4870

4971
expect(first).toBeDefined();
5072
expect(second).toBeDefined();
5173
expect(second).not.toBe(first);
5274
});
5375

54-
it("changes when auth profile credentials change", async () => {
76+
it("keeps oauth auth-profile epochs stable across access-token refreshes", async () => {
5577
let store: AuthProfileStore = {
5678
version: 1,
5779
profiles: {
@@ -80,6 +102,48 @@ describe("resolveCliAuthEpoch", () => {
80102
provider: "anthropic",
81103
access: "access-b",
82104
refresh: "refresh",
105+
expires: 2,
106+
},
107+
},
108+
};
109+
const second = await resolveCliAuthEpoch({
110+
provider: "google-gemini-cli",
111+
authProfileId: "anthropic:work",
112+
});
113+
114+
expect(first).toBeDefined();
115+
expect(second).toBe(first);
116+
});
117+
118+
it("changes oauth auth-profile epochs when the refresh token changes", async () => {
119+
let store: AuthProfileStore = {
120+
version: 1,
121+
profiles: {
122+
"anthropic:work": {
123+
type: "oauth",
124+
provider: "anthropic",
125+
access: "access",
126+
refresh: "refresh-a",
127+
expires: 1,
128+
},
129+
},
130+
};
131+
setCliAuthEpochTestDeps({
132+
loadAuthProfileStoreForRuntime: () => store,
133+
});
134+
135+
const first = await resolveCliAuthEpoch({
136+
provider: "google-gemini-cli",
137+
authProfileId: "anthropic:work",
138+
});
139+
store = {
140+
version: 1,
141+
profiles: {
142+
"anthropic:work": {
143+
type: "oauth",
144+
provider: "anthropic",
145+
access: "access",
146+
refresh: "refresh-b",
83147
expires: 1,
84148
},
85149
},
@@ -96,13 +160,14 @@ describe("resolveCliAuthEpoch", () => {
96160

97161
it("mixes local codex and auth-profile state", async () => {
98162
let access = "local-access-a";
163+
let localRefresh = "local-refresh-a";
99164
let refresh = "profile-refresh-a";
100165
setCliAuthEpochTestDeps({
101166
readCodexCliCredentialsCached: () => ({
102167
type: "oauth",
103168
provider: "openai-codex",
104169
access,
105-
refresh: "local-refresh",
170+
refresh: localRefresh,
106171
expires: 1,
107172
accountId: "acct-1",
108173
}),
@@ -129,17 +194,23 @@ describe("resolveCliAuthEpoch", () => {
129194
provider: "codex-cli",
130195
authProfileId: "openai:work",
131196
});
132-
refresh = "profile-refresh-b";
197+
localRefresh = "local-refresh-b";
133198
const third = await resolveCliAuthEpoch({
134199
provider: "codex-cli",
135200
authProfileId: "openai:work",
136201
});
202+
refresh = "profile-refresh-b";
203+
const fourth = await resolveCliAuthEpoch({
204+
provider: "codex-cli",
205+
authProfileId: "openai:work",
206+
});
137207

138208
expect(first).toBeDefined();
139-
expect(second).toBeDefined();
140209
expect(third).toBeDefined();
141-
expect(second).not.toBe(first);
210+
expect(fourth).toBeDefined();
211+
expect(second).toBe(first);
142212
expect(third).not.toBe(second);
213+
expect(fourth).not.toBe(third);
143214
});
144215

145216
it("can ignore local codex state when the backend is profile-owned", async () => {

0 commit comments

Comments
 (0)