























@@ -2,13 +2,18 @@ import chokidar from "chokidar";
22import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33import { listChannelPlugins } from "../channels/plugins/index.js";
44import type { ChannelPlugin } from "../channels/plugins/types.js";
5+import {
6+getSkillsSnapshotVersion,
7+resetSkillsRefreshStateForTest,
8+} from "../agents/skills/refresh-state.js";
59import type { ConfigFileSnapshot, ConfigWriteNotification } from "../config/config.js";
610import { setActivePluginRegistry } from "../plugins/runtime.js";
711import { createTestRegistry } from "../test-utils/channel-plugins.js";
812import {
913buildGatewayReloadPlan,
1014diffConfigPaths,
1115resolveGatewayReloadSettings,
16+shouldInvalidateSkillsSnapshotForPaths,
1217startGatewayConfigReloader,
1318} from "./config-reload.js";
1419@@ -616,3 +621,98 @@ describe("startGatewayConfigReloader", () => {
616621await harness.reloader.stop();
617622});
618623});
624+625+describe("shouldInvalidateSkillsSnapshotForPaths", () => {
626+it.each([
627+"skills",
628+"skills.allowBundled",
629+"skills.entries",
630+"skills.entries.himalaya",
631+"skills.entries.himalaya.enabled",
632+"skills.profile",
633+])("returns true for skills path %s", (path) => {
634+expect(shouldInvalidateSkillsSnapshotForPaths([path])).toBe(true);
635+});
636+637+it.each([
638+"tools.profile",
639+"agents.defaults.model",
640+"gateway.port",
641+"skillset.allowBundled",
642+"channels.telegram.enabled",
643+])("returns false for unrelated path %s", (path) => {
644+expect(shouldInvalidateSkillsSnapshotForPaths([path])).toBe(false);
645+});
646+647+it("returns true when any path in the list matches", () => {
648+expect(
649+shouldInvalidateSkillsSnapshotForPaths([
650+"gateway.port",
651+"skills.allowBundled",
652+"channels.telegram.enabled",
653+]),
654+).toBe(true);
655+});
656+657+it("returns false for empty input", () => {
658+expect(shouldInvalidateSkillsSnapshotForPaths([])).toBe(false);
659+});
660+});
661+662+describe("startGatewayConfigReloader skills invalidation", () => {
663+beforeEach(() => {
664+vi.useFakeTimers();
665+resetSkillsRefreshStateForTest();
666+});
667+668+afterEach(() => {
669+vi.useRealTimers();
670+vi.restoreAllMocks();
671+resetSkillsRefreshStateForTest();
672+});
673+674+it("bumps the skills snapshot version when skills.allowBundled changes", async () => {
675+const before = getSkillsSnapshotVersion();
676+const readSnapshot = vi.fn<() => Promise<ConfigFileSnapshot>>().mockResolvedValueOnce(
677+makeSnapshot({
678+config: {
679+gateway: { reload: { debounceMs: 0 } },
680+skills: { allowBundled: ["gog"] },
681+},
682+hash: "skills-change-1",
683+}),
684+);
685+const { watcher, log, reloader } = createReloaderHarness(readSnapshot);
686+687+watcher.emit("change");
688+await vi.runOnlyPendingTimersAsync();
689+690+const after = getSkillsSnapshotVersion();
691+expect(after).toBeGreaterThan(before);
692+expect(log.info).toHaveBeenCalledWith(
693+expect.stringContaining("skills snapshot invalidated by config change"),
694+);
695+696+await reloader.stop();
697+});
698+699+it("does not bump the snapshot version when unrelated config changes", async () => {
700+const before = getSkillsSnapshotVersion();
701+const readSnapshot = vi.fn<() => Promise<ConfigFileSnapshot>>().mockResolvedValueOnce(
702+makeSnapshot({
703+config: {
704+gateway: { reload: { debounceMs: 0 }, port: 18790 },
705+},
706+hash: "unrelated-change-1",
707+}),
708+);
709+const { watcher, reloader } = createReloaderHarness(readSnapshot);
710+711+watcher.emit("change");
712+await vi.runOnlyPendingTimersAsync();
713+714+expect(getSkillsSnapshotVersion()).toBe(before);
715+716+await reloader.stop();
717+});
718+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。