mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 03:12:42 +00:00
fix: pairing admin satisfies write (#23125) (thanks @vignesh07)
This commit is contained in:
committed by
Vignesh
parent
426d97797d
commit
5b4409d5d0
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.
|
||||||
- Memory/QMD: add optional `memory.qmd.mcporter` search routing so QMD `query/search/vsearch` can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07.
|
- Memory/QMD: add optional `memory.qmd.mcporter` search routing so QMD `query/search/vsearch` can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07.
|
||||||
- Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:<id>]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.
|
- Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:<id>]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.
|
||||||
- BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.
|
- BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.
|
||||||
|
|||||||
@@ -196,6 +196,55 @@ describe("gateway lock", () => {
|
|||||||
staleSpy.mockRestore();
|
staleSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps lock when fs.stat fails until payload is stale", async () => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
const env = await makeEnv();
|
||||||
|
const { lockPath, configPath } = resolveLockPath(env);
|
||||||
|
const payload = createLockPayload({ configPath, startTime: 111 });
|
||||||
|
await fs.writeFile(lockPath, JSON.stringify(payload), "utf8");
|
||||||
|
|
||||||
|
const procSpy = mockProcStatRead({
|
||||||
|
onProcRead: () => {
|
||||||
|
throw new Error("EACCES");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const statSpy = vi
|
||||||
|
.spyOn(fs, "stat")
|
||||||
|
.mockRejectedValue(Object.assign(new Error("EPERM"), { code: "EPERM" }));
|
||||||
|
|
||||||
|
const pending = acquireForTest(env, {
|
||||||
|
timeoutMs: 20,
|
||||||
|
staleMs: 10_000,
|
||||||
|
platform: "linux",
|
||||||
|
});
|
||||||
|
await expect(pending).rejects.toBeInstanceOf(GatewayLockError);
|
||||||
|
|
||||||
|
procSpy.mockRestore();
|
||||||
|
|
||||||
|
const stalePayload = createLockPayload({
|
||||||
|
configPath,
|
||||||
|
startTime: 111,
|
||||||
|
createdAt: new Date(0).toISOString(),
|
||||||
|
});
|
||||||
|
await fs.writeFile(lockPath, JSON.stringify(stalePayload), "utf8");
|
||||||
|
|
||||||
|
const staleProcSpy = mockProcStatRead({
|
||||||
|
onProcRead: () => {
|
||||||
|
throw new Error("EACCES");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lock = await acquireForTest(env, {
|
||||||
|
staleMs: 1,
|
||||||
|
platform: "linux",
|
||||||
|
});
|
||||||
|
expect(lock).not.toBeNull();
|
||||||
|
|
||||||
|
await lock?.release();
|
||||||
|
staleProcSpy.mockRestore();
|
||||||
|
statSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
it("returns null when multi-gateway override is enabled", async () => {
|
it("returns null when multi-gateway override is enabled", async () => {
|
||||||
const env = await makeEnv();
|
const env = await makeEnv();
|
||||||
const lock = await acquireGatewayLock({
|
const lock = await acquireGatewayLock({
|
||||||
|
|||||||
@@ -231,7 +231,11 @@ export async function acquireGatewayLock(
|
|||||||
const st = await fs.stat(lockPath);
|
const st = await fs.stat(lockPath);
|
||||||
stale = Date.now() - st.mtimeMs > staleMs;
|
stale = Date.now() - st.mtimeMs > staleMs;
|
||||||
} catch {
|
} catch {
|
||||||
stale = true;
|
// On Windows or locked filesystems we may be unable to stat the
|
||||||
|
// lock file even though the existing gateway is still healthy.
|
||||||
|
// Treat the lock as non-stale so we keep waiting instead of
|
||||||
|
// forcefully removing another gateway's lock.
|
||||||
|
stale = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stale) {
|
if (stale) {
|
||||||
|
|||||||
@@ -985,8 +985,9 @@ describe("QmdMemoryManager", () => {
|
|||||||
);
|
);
|
||||||
expect(mcporterCall).toBeDefined();
|
expect(mcporterCall).toBeDefined();
|
||||||
const spawnOpts = mcporterCall?.[2] as { env?: NodeJS.ProcessEnv } | undefined;
|
const spawnOpts = mcporterCall?.[2] as { env?: NodeJS.ProcessEnv } | undefined;
|
||||||
expect(spawnOpts?.env?.XDG_CONFIG_HOME).toContain("/agents/main/qmd/xdg-config");
|
const normalizePath = (value?: string) => value?.replace(/\\/g, "/");
|
||||||
expect(spawnOpts?.env?.XDG_CACHE_HOME).toContain("/agents/main/qmd/xdg-cache");
|
expect(normalizePath(spawnOpts?.env?.XDG_CONFIG_HOME)).toContain("/agents/main/qmd/xdg-config");
|
||||||
|
expect(normalizePath(spawnOpts?.env?.XDG_CACHE_HOME)).toContain("/agents/main/qmd/xdg-cache");
|
||||||
|
|
||||||
await manager.close();
|
await manager.close();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user