惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
博客园 - 司徒正美
A
About on SuperTechFans
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
F
Fortinet All Blogs
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
Jina AI
Jina AI
H
Help Net Security
量子位
IT之家
IT之家

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(gateway): cache effective tool inventory · openclaw/o...
steipete · 2026-04-27 · via Recent Commits to openclaw:main

@@ -1,6 +1,6 @@

11

import { beforeEach, describe, expect, it, vi } from "vitest";

22

import { ErrorCodes } from "../protocol/index.js";

3-

import { toolsEffectiveHandlers } from "./tools-effective.js";

3+

import { __testing, toolsEffectiveHandlers } from "./tools-effective.js";

4455

const runtimeMocks = vi.hoisted(() => ({

66

deliveryContextFromSession: vi.fn(() => ({

@@ -29,6 +29,7 @@ const runtimeMocks = vi.hoisted(() => ({

2929

model: "gpt-4.1",

3030

},

3131

})),

32+

getActivePluginRegistryVersion: vi.fn(() => 1),

3233

resolveEffectiveToolInventory: vi.fn(() => ({

3334

agentId: "main",

3435

profile: "coding",

@@ -77,6 +78,9 @@ function createInvokeParams(params: Record<string, unknown>) {

7778

describe("tools.effective handler", () => {

7879

beforeEach(() => {

7980

vi.clearAllMocks();

81+

__testing.resetToolsEffectiveCacheForTest();

82+

__testing.resetToolsEffectiveNowForTest();

83+

runtimeMocks.getActivePluginRegistryVersion.mockReturnValue(1);

8084

});

81858286

it("rejects invalid params", async () => {

@@ -167,6 +171,93 @@ describe("tools.effective handler", () => {

167171

);

168172

});

169173174+

it("serves repeated requests from the fresh inventory cache", async () => {

175+

const first = createInvokeParams({ sessionKey: "main:abc" });

176+

await first.invoke();

177+

const second = createInvokeParams({ sessionKey: "main:abc" });

178+

await second.invoke();

179+180+

expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(1);

181+

expect((first.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);

182+

expect((second.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);

183+

});

184+185+

it("coalesces identical cache misses while inventory resolution is pending", async () => {

186+

const first = createInvokeParams({ sessionKey: "main:abc" });

187+

const second = createInvokeParams({ sessionKey: "main:abc" });

188+189+

await Promise.all([first.invoke(), second.invoke()]);

190+191+

expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(1);

192+

expect((first.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);

193+

expect((second.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);

194+

});

195+196+

it("returns stale cached inventory immediately while refreshing in the background", async () => {

197+

let now = 1_000;

198+

__testing.setToolsEffectiveNowForTest(() => now);

199+

const stalePayload = {

200+

agentId: "main",

201+

profile: "coding",

202+

groups: [

203+

{

204+

id: "core",

205+

label: "Built-in tools",

206+

source: "core",

207+

tools: [

208+

{

209+

id: "read",

210+

label: "Read",

211+

description: "Read files",

212+

rawDescription: "Read files",

213+

source: "core",

214+

},

215+

],

216+

},

217+

],

218+

};

219+

const refreshedPayload = {

220+

agentId: "main",

221+

profile: "coding",

222+

groups: [

223+

{

224+

id: "core",

225+

label: "Built-in tools",

226+

source: "core",

227+

tools: [

228+

{

229+

id: "exec",

230+

label: "Exec",

231+

description: "Run shell commands",

232+

rawDescription: "Run shell commands",

233+

source: "core",

234+

},

235+

],

236+

},

237+

],

238+

};

239+

runtimeMocks.resolveEffectiveToolInventory

240+

.mockReturnValueOnce(stalePayload)

241+

.mockReturnValueOnce(refreshedPayload);

242+243+

const initial = createInvokeParams({ sessionKey: "main:abc" });

244+

await initial.invoke();

245+

now += 11_000;

246+247+

const stale = createInvokeParams({ sessionKey: "main:abc" });

248+

await stale.invoke();

249+250+

expect((stale.respond.mock.calls[0] as RespondCall | undefined)?.[1]).toBe(stalePayload);

251+

expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(1);

252+253+

await new Promise<void>((resolve) => setImmediate(resolve));

254+

expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(2);

255+256+

const fresh = createInvokeParams({ sessionKey: "main:abc" });

257+

await fresh.invoke();

258+

expect((fresh.respond.mock.calls[0] as RespondCall | undefined)?.[1]).toBe(refreshedPayload);

259+

});

260+170261

it("falls back to origin.threadId when delivery context omits thread metadata", async () => {

171262

runtimeMocks.loadSessionEntry.mockReturnValueOnce({

172263

cfg: {},