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

推荐订阅源

Recent Announcements
Recent Announcements
J
Java Code Geeks
U
Unit 42
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
D
Docker
F
Fortinet All Blogs
N
Netflix TechBlog - Medium
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
罗磊的独立博客
I
InfoQ
The Cloudflare Blog
小众软件
小众软件
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
S
SegmentFault 最新的问题
爱范儿
爱范儿
Hugging Face - Blog
Hugging Face - Blog
P
Proofpoint News Feed
V
V2EX
月光博客
月光博客
Martin Fowler
Martin Fowler

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
Merge branch 'main' of https://github.com/openclaw/opencl...
vincentkoc · 2026-05-03 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -29,7 +29,7 @@ updates:

2929

update-types:

3030

- minor

3131

- patch

32-

open-pull-requests-limit: 10

32+

open-pull-requests-limit: 20

3333

registries:

3434

- npm-npmjs

3535
Original file line numberDiff line numberDiff line change

@@ -1,4 +1,4 @@

1-

0b39ec73150d957acdc4dac3fe0b468998e74bb602750d31b1ecfd6eda7d6dc5 config-baseline.json

2-

6d477ca3b60982b770e85929ab8393a7923a6b31ce99f3b7c7dba13cdd4f9180 config-baseline.core.json

1+

2f2463daef1ea612d58e6bf0148d928d884a331053ad1063f9d18b0f753f51a1 config-baseline.json

2+

b95cc4a3b15e688afa58cf700dac425af4e50fd092209e75903e73d5d2988717 config-baseline.core.json

33

a2a949a99f5cc5960d4d7ae0159b6b48c4d6b1f813be67cda196457ab2f88034 config-baseline.channel.json

44

fffe0e74eab92a88c3c57952a70bc932438ce3a7f5f9982688437f2cdaee0bcb config-baseline.plugin.json

Original file line numberDiff line numberDiff line change

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

11

// Barnacle owns deterministic GitHub triage and auto-response behavior.

22
3-

const activePrLimit = 10;

3+

const activePrLimit = 20;

44
55

const thirdPartyExtensionMessage =

66

"Please publish this as a third-party plugin on [ClawHub](https://clawhub.ai) instead of adding it to the core repo. Docs: https://docs.openclaw.ai/plugin and https://docs.openclaw.ai/tools/clawhub";

@@ -80,7 +80,7 @@ export const managedLabelSpecs = {

8080

},

8181

"r: too-many-prs": {

8282

color: "D93F0B",

83-

description: "Auto-close: author has more than ten active PRs.",

83+

description: "Auto-close: author has more than twenty active PRs.",

8484

},

8585

"r: too-many-prs-override": {

8686

color: "C2E0C6",

Original file line numberDiff line numberDiff line change

@@ -271,6 +271,53 @@ describe("plugins cli uninstall", () => {

271271

});

272272

});

273273
274+

it("cleans stale policy refs even when plugin is absent from the current registry", async () => {

275+

const baseConfig = {

276+

plugins: {

277+

allow: ["alpha", "beta"],

278+

deny: ["alpha"],

279+

},

280+

} as OpenClawConfig;

281+

const nextConfig = {

282+

plugins: {

283+

allow: ["beta"],

284+

},

285+

} as OpenClawConfig;

286+
287+

loadConfig.mockReturnValue(baseConfig);

288+

buildPluginSnapshotReport.mockReturnValue({

289+

plugins: [],

290+

diagnostics: [],

291+

});

292+

planPluginUninstall.mockReturnValue({

293+

ok: true,

294+

config: nextConfig,

295+

actions: {

296+

entry: false,

297+

install: false,

298+

allowlist: true,

299+

denylist: true,

300+

loadPath: false,

301+

memorySlot: false,

302+

contextEngineSlot: false,

303+

channelConfig: false,

304+

directory: false,

305+

},

306+

directoryRemoval: null,

307+

});

308+
309+

await runPluginsCommand(["plugins", "uninstall", "alpha", "--force"]);

310+
311+

expect(planPluginUninstall).toHaveBeenCalledWith(

312+

expect.objectContaining({

313+

pluginId: "alpha",

314+

deleteFiles: true,

315+

}),

316+

);

317+

expect(writeConfigFile).toHaveBeenCalledWith(nextConfig);

318+

expect(runtimeLogs.at(-2)).toContain('Uninstalled plugin "alpha"');

319+

});

320+
274321

it("exits when uninstall target is not managed by plugin install records", async () => {

275322

loadConfig.mockReturnValue({

276323

plugins: {

@@ -282,12 +329,16 @@ describe("plugins cli uninstall", () => {

282329

plugins: [{ id: "alpha", name: "alpha" }],

283330

diagnostics: [],

284331

});

332+

planPluginUninstall.mockReturnValue({

333+

ok: false,

334+

error: "Plugin not found: alpha",

335+

});

285336
286337

await expect(runPluginsCommand(["plugins", "uninstall", "alpha", "--force"])).rejects.toThrow(

287338

"__exit__:1",

288339

);

289340
290341

expect(runtimeErrors.at(-1)).toContain("is not managed by plugins config/install records");

291-

expect(planPluginUninstall).not.toHaveBeenCalled();

342+

expect(planPluginUninstall).toHaveBeenCalled();

292343

});

293344

});

Original file line numberDiff line numberDiff line change

@@ -74,21 +74,6 @@ export async function runPluginUninstallCommand(

7474

config: cfg,

7575

plugins: report.plugins,

7676

});

77-

const hasEntry = pluginId in (cfg.plugins?.entries ?? {});

78-

const hasInstall = pluginId in (cfg.plugins?.installs ?? {});

79-
80-

if (!hasEntry && !hasInstall) {

81-

if (plugin) {

82-

runtime.error(

83-

`Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`,

84-

);

85-

} else {

86-

runtime.error(`Plugin not found: ${id}`);

87-

}

88-

runtime.exit(1);

89-

return;

90-

}

91-
9277

const channelIds = plugin?.status === "loaded" ? plugin.channelIds : undefined;

9378

const plan = planPluginUninstall({

9479

config: cfg,

@@ -98,10 +83,17 @@ export async function runPluginUninstallCommand(

9883

extensionsDir,

9984

});

10085

if (!plan.ok) {

101-

runtime.error(plan.error);

86+

if (plugin) {

87+

runtime.error(

88+

`Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`,

89+

);

90+

} else {

91+

runtime.error(plan.error);

92+

}

10293

runtime.exit(1);

10394

return;

10495

}

96+

const hasInstall = pluginId in (cfg.plugins?.installs ?? {});

10597
10698

const preview: string[] = [];

10799

if (plan.actions.entry) {

Original file line numberDiff line numberDiff line change

@@ -40,10 +40,10 @@ export type ClawHubPackageEnvironmentSummary = {

4040

knownUnsupported?: string[];

4141

};

4242

export type ClawHubPackageArtifactSummary = {

43-

kind?: "legacy-zip" | "npm-pack" | null;

43+

kind?: string | null;

4444

sha256?: string | null;

4545

size?: number | null;

46-

format?: "zip" | "tgz" | null;

46+

format?: string | null;

4747

npmIntegrity?: string | null;

4848

npmShasum?: string | null;

4949

npmTarballName?: string | null;

Original file line numberDiff line numberDiff line change

@@ -26,10 +26,18 @@ type TsdownOnLog = (

2626

) => void;

2727
2828

type TsdownInputOptions = (

29-

options: { onLog?: TsdownOnLog },

29+

options: { external?: TsdownExternalOption; onLog?: TsdownOnLog },

3030

format?: unknown,

3131

context?: unknown,

32-

) => { onLog?: TsdownOnLog } | undefined;

32+

) => { external?: TsdownExternalOption; onLog?: TsdownOnLog } | undefined;

33+
34+

type TsdownExternalOption = string | RegExp | Array<string | RegExp> | TsdownExternalFunction;

35+
36+

type TsdownExternalFunction = (

37+

id: string,

38+

parentId: string | undefined,

39+

isResolved: boolean,

40+

) => boolean | null | undefined;

3341
3442

function asConfigArray(config: unknown): TsdownConfigEntry[] {

3543

return Array.isArray(config) ? (config as TsdownConfigEntry[]) : [config as TsdownConfigEntry];

@@ -135,15 +143,22 @@ describe("tsdown config", () => {

135143

it("externalizes known heavy native dependencies", () => {

136144

const unifiedGraph = unifiedDistGraph();

137145

const neverBundle = unifiedGraph?.deps?.neverBundle;

146+

const external = unifiedGraph?.inputOptions?.({})?.external;

138147
139148

if (typeof neverBundle === "function") {

140149

expect(neverBundle("@lancedb/lancedb")).toBe(true);

141150

expect(neverBundle("@matrix-org/matrix-sdk-crypto-nodejs")).toBe(true);

142151

expect(neverBundle("matrix-js-sdk/lib/client.js")).toBe(true);

152+

expect(neverBundle("qrcode-terminal/lib/main.js")).toBe(true);

143153

expect(neverBundle("not-a-runtime-dependency")).toBe(false);

144154

} else {

145-

expect(neverBundle).toEqual(expect.arrayContaining(["@lancedb/lancedb", "matrix-js-sdk"]));

155+

expect(neverBundle).toEqual(

156+

expect.arrayContaining(["@lancedb/lancedb", "matrix-js-sdk", "qrcode-terminal"]),

157+

);

146158

}

159+

expect(typeof external).toBe("function");

160+

const externalize = external as TsdownExternalFunction;

161+

expect(externalize("qrcode-terminal/lib/main.js", undefined, false)).toBe(true);

147162

});

148163
149164

it("suppresses unresolved imports from extension source", () => {

Original file line numberDiff line numberDiff line change

@@ -704,6 +704,37 @@ describe("uninstallPlugin", () => {

704704

}

705705

});

706706
707+

it("cleans stale policy references even when plugin code and install records are gone", async () => {

708+

const result = await uninstallPlugin({

709+

config: createPluginConfig({

710+

allow: ["missing-plugin", "other-plugin"],

711+

deny: ["missing-plugin"],

712+

slots: {

713+

memory: "missing-plugin",

714+

},

715+

}),

716+

pluginId: "missing-plugin",

717+

deleteFiles: true,

718+

});

719+
720+

const successfulResult = expectSuccessfulUninstall(result);

721+

expect(successfulResult.actions).toEqual({

722+

entry: false,

723+

install: false,

724+

allowlist: true,

725+

denylist: true,

726+

loadPath: false,

727+

memorySlot: true,

728+

contextEngineSlot: false,

729+

channelConfig: false,

730+

directory: false,

731+

});

732+

expect(successfulResult.config.plugins?.allow).toEqual(["other-plugin"]);

733+

expect(successfulResult.config.plugins?.deny).toBeUndefined();

734+

expect(successfulResult.config.plugins?.slots?.memory).toBe("memory-core");

735+

expect(runCommandWithTimeoutMock).not.toHaveBeenCalled();

736+

});

737+
707738

it("removes config entries", async () => {

708739

const config = createPluginConfig({

709740

entries: createSinglePluginEntries(),

@@ -829,6 +860,24 @@ describe("uninstallPlugin", () => {

829860

await expect(fs.access(pluginDir)).rejects.toThrow();

830861

});

831862
863+

it("skips npm cleanup when the managed package directory is already absent", async () => {

864+

const stateDir = path.join(tempDir, "state");

865+

const npmRoot = path.join(stateDir, "npm");

866+

const pluginDir = path.join(npmRoot, "node_modules", "missing-plugin");

867+
868+

const applied = await applyPluginUninstallDirectoryRemoval({

869+

target: pluginDir,

870+

cleanup: {

871+

kind: "npm",

872+

npmRoot,

873+

packageName: "missing-plugin",

874+

},

875+

});

876+
877+

expect(applied).toEqual({ directoryRemoved: false, warnings: [] });

878+

expect(runCommandWithTimeoutMock).not.toHaveBeenCalled();

879+

});

880+
832881

it("warns and still removes npm package dirs when npm prune cleanup fails", async () => {

833882

runCommandWithTimeoutMock.mockResolvedValueOnce({

834883

code: 1,

Original file line numberDiff line numberDiff line change

@@ -77,6 +77,10 @@ export function formatUninstallActionLabels(actions: UninstallActions): string[]

7777

);

7878

}

7979
80+

function hasUninstallAction(actions: Omit<UninstallActions, "directory">): boolean {

81+

return Object.values(actions).some(Boolean);

82+

}

83+
8084

export function formatUninstallSlotResetPreview(slotKey: "memory" | "contextEngine"): string {

8185

const actionKey = slotKey === "memory" ? "memorySlot" : "contextEngineSlot";

8286

return `${UNINSTALL_ACTION_LABELS[actionKey]} (will reset to "${defaultSlotIdForKey(slotKey)}")`;

@@ -489,14 +493,8 @@ export type UninstallPluginParams = {

489493

export function planPluginUninstall(params: UninstallPluginParams): PluginUninstallPlanResult {

490494

const { config, pluginId, channelIds, deleteFiles = true, extensionsDir } = params;

491495
492-

// Validate plugin exists

493496

const hasEntry = pluginId in (config.plugins?.entries ?? {});

494497

const hasInstall = pluginId in (config.plugins?.installs ?? {});

495-
496-

if (!hasEntry && !hasInstall) {

497-

return { ok: false, error: `Plugin not found: ${pluginId}` };

498-

}

499-
500498

const installRecord = config.plugins?.installs?.[pluginId];

501499

const isLinked = isLinkedPathInstallRecord(installRecord);

502500

@@ -505,6 +503,10 @@ export function planPluginUninstall(params: UninstallPluginParams): PluginUninst

505503

channelIds,

506504

});

507505
506+

if (!hasEntry && !hasInstall && !hasUninstallAction(configActions)) {

507+

return { ok: false, error: `Plugin not found: ${pluginId}` };

508+

}

509+
508510

const actions: UninstallActions = {

509511

...configActions,

510512

directory: false,

@@ -563,6 +565,10 @@ export async function applyPluginUninstallDirectoryRemoval(

563565

.then(() => true)

564566

.catch(() => false)) ?? false;

565567

const warnings: string[] = [];

568+

if (!existed) {

569+

return { directoryRemoved: false, warnings };

570+

}

571+
566572

if (removal.cleanup?.kind === "npm") {

567573

const uninstall = await runCommandWithTimeout(

568574

[

Original file line numberDiff line numberDiff line change

@@ -204,7 +204,7 @@ describe("barnacle-auto-response", () => {

204204

expect(managedLabelSpecs["r: support"].description).toContain("support requests");

205205

expect(managedLabelSpecs["r: false-positive"].description).toContain("false positive");

206206

expect(managedLabelSpecs["r: third-party-extension"].description).toContain("ClawHub");

207-

expect(managedLabelSpecs["r: too-many-prs"].description).toContain("ten active PRs");

207+

expect(managedLabelSpecs["r: too-many-prs"].description).toContain("twenty active PRs");

208208
209209

for (const label of Object.values(candidateLabels)) {

210210

expect(managedLabelSpecs[label]).toBeDefined();

@@ -495,7 +495,7 @@ describe("barnacle-auto-response", () => {

495495

);

496496

expect(calls.createComment).not.toContainEqual(

497497

expect.objectContaining({

498-

body: expect.stringContaining("more than 10 active PRs"),

498+

body: expect.stringContaining("more than 20 active PRs"),

499499

}),

500500

);

501501

expect(calls.update).not.toContainEqual(expect.objectContaining({ state: "closed" }));