





















@@ -31,6 +31,15 @@ const memoryCoreCommandAliasRegistry: PluginManifestCommandAliasRegistry = {
3131],
3232};
333334+const losslessClawToolRegistry: PluginManifestCommandAliasRegistry = {
35+plugins: [
36+{
37+id: "lossless-claw",
38+contracts: { tools: ["lcm_recent", "lcm_search"] },
39+},
40+],
41+};
42+3443describe("isGatewayRunFastPathArgv", () => {
3544it("matches only plain gateway foreground starts without root options or help", () => {
3645expect(isGatewayRunFastPathArgv(["node", "openclaw", "gateway"])).toBe(true);
@@ -366,4 +375,101 @@ describe("resolveMissingPluginCommandMessage", () => {
366375expect(message).toContain('"memory-wiki"');
367376expect(message).toContain("plugins.allow");
368377});
378+379+it("identifies an agent tool name and points the user at model tool-use", () => {
380+const message = resolveMissingPluginCommandMessage(
381+"lcm_recent",
382+{
383+plugins: {
384+allow: ["lossless-claw"],
385+},
386+},
387+{ registry: losslessClawToolRegistry },
388+);
389+expect(message).not.toBeNull();
390+expect(message).toContain('"lcm_recent"');
391+expect(message).toContain('"lossless-claw"');
392+expect(message).toContain("agent tool");
393+expect(message).not.toContain("plugins.allow");
394+});
395+396+it("matches agent tool names case-insensitively", () => {
397+const message = resolveMissingPluginCommandMessage("LCM_Recent", undefined, {
398+registry: losslessClawToolRegistry,
399+});
400+expect(message).not.toBeNull();
401+expect(message).toContain("agent tool");
402+expect(message).toContain('"lossless-claw"');
403+});
404+405+it("preserves the plugins.allow suggestion when the unknown name is not a plugin tool", () => {
406+const message = resolveMissingPluginCommandMessage(
407+"totally-unknown",
408+{
409+plugins: {
410+allow: ["quietchat"],
411+},
412+},
413+{ registry: losslessClawToolRegistry },
414+);
415+expect(message).not.toBeNull();
416+expect(message).toContain('`plugins.allow` excludes "totally-unknown"');
417+});
418+419+it("does not attribute a tool to an owning plugin excluded by plugins.allow", () => {
420+// The owning plugin is denied via plugins.allow, so the manifest-declared
421+// tool is not available through the owning plugin. Fall through to the
422+// standard plugins.allow message instead of falsely attributing it.
423+const message = resolveMissingPluginCommandMessage(
424+"lcm_recent",
425+{
426+plugins: {
427+allow: ["quietchat"],
428+},
429+},
430+{ registry: losslessClawToolRegistry },
431+);
432+expect(message).not.toBeNull();
433+expect(message).not.toContain("agent tool available");
434+expect(message).toContain('`plugins.allow` excludes "lcm_recent"');
435+});
436+437+it("does not attribute a tool to an owning plugin disabled via plugins.entries", () => {
438+const message = resolveMissingPluginCommandMessage(
439+"lcm_recent",
440+{
441+plugins: {
442+entries: {
443+"lossless-claw": { enabled: false },
444+},
445+},
446+},
447+{ registry: losslessClawToolRegistry },
448+);
449+// entries.<id>.enabled = false on the OWNING plugin invalidates the
450+// plugin-tool attribution. With no allow filter on the bare name the
451+// diagnostic returns null (no actionable message); callers handle that
452+// as "not a recognised plugin command".
453+expect(message).toBeNull();
454+});
455+456+it("uses softer 'may be provided by' wording for manifest-only availability", () => {
457+// Some runtime gates (per-account enabled, per-tool toggles in the Feishu
458+// family etc.) cannot be expressed as manifest configSignals, so the
459+// runtime resolver reports availability: "manifest-only" when ownership is
460+// only manifest-provable. The diagnostic must avoid asserting "registered
461+// by" in that case.
462+const manifestOnlyOwner = {
463+toolName: "feishu_chat",
464+pluginId: "feishu",
465+availability: "manifest-only" as const,
466+};
467+const message = resolveMissingPluginCommandMessage("feishu_chat", undefined, {
468+resolveToolOwner: () => manifestOnlyOwner,
469+});
470+expect(message).not.toBeNull();
471+expect(message).toContain("may be provided by");
472+expect(message).toContain('"feishu"');
473+expect(message).not.toContain("registered by");
474+});
369475});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。