






















@@ -0,0 +1,155 @@
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+import type { OpenClawConfig, PluginRuntime } from "../runtime-api.js";
6+import { maybeCreateDynamicAgent } from "./dynamic-agent.js";
7+8+let tempRoot: string;
9+10+beforeEach(async () => {
11+tempRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-feishu-agent-"));
12+});
13+14+afterEach(async () => {
15+await fs.promises.rm(tempRoot, { recursive: true, force: true });
16+});
17+18+function createRuntime() {
19+const replaceConfigFile = vi.fn(async () => {});
20+return {
21+runtime: {
22+config: {
23+ replaceConfigFile,
24+},
25+} as unknown as PluginRuntime,
26+ replaceConfigFile,
27+};
28+}
29+30+function createDynamicConfig() {
31+return {
32+enabled: true,
33+workspaceTemplate: path.join(tempRoot, "workspace-{agentId}"),
34+agentDirTemplate: path.join(tempRoot, "agent-{agentId}"),
35+};
36+}
37+38+async function pathExists(target: string): Promise<boolean> {
39+return fs.promises
40+.stat(target)
41+.then(() => true)
42+.catch((err: unknown) => {
43+if ((err as NodeJS.ErrnoException).code === "ENOENT") {
44+return false;
45+}
46+throw err;
47+});
48+}
49+50+describe("maybeCreateDynamicAgent", () => {
51+it("does not persist dynamic agents when config writes are disabled", async () => {
52+const { runtime, replaceConfigFile } = createRuntime();
53+const dynamicCfg = createDynamicConfig();
54+55+const result = await maybeCreateDynamicAgent({
56+cfg: {
57+channels: { feishu: { configWrites: false } },
58+agents: { list: [] },
59+bindings: [],
60+} as OpenClawConfig,
61+ runtime,
62+senderOpenId: "ou_sender",
63+ dynamicCfg,
64+configWritesAllowed: false,
65+log: vi.fn(),
66+});
67+68+expect(result).toEqual({
69+created: false,
70+updatedCfg: {
71+channels: { feishu: { configWrites: false } },
72+agents: { list: [] },
73+bindings: [],
74+},
75+});
76+expect(replaceConfigFile).not.toHaveBeenCalled();
77+expect(await pathExists(path.join(tempRoot, "workspace-feishu-ou_sender"))).toBe(false);
78+expect(await pathExists(path.join(tempRoot, "agent-feishu-ou_sender"))).toBe(false);
79+});
80+81+it("persists a sender agent and direct binding when config writes are allowed", async () => {
82+const { runtime, replaceConfigFile } = createRuntime();
83+84+const result = await maybeCreateDynamicAgent({
85+cfg: {
86+agents: { list: [] },
87+bindings: [],
88+} as OpenClawConfig,
89+ runtime,
90+senderOpenId: "ou_sender",
91+dynamicCfg: createDynamicConfig(),
92+configWritesAllowed: true,
93+log: vi.fn(),
94+});
95+96+expect(result.created).toBe(true);
97+expect(result.agentId).toBe("feishu-ou_sender");
98+expect(replaceConfigFile).toHaveBeenCalledTimes(1);
99+expect(replaceConfigFile).toHaveBeenCalledWith({
100+nextConfig: expect.objectContaining({
101+agents: {
102+list: [
103+{
104+id: "feishu-ou_sender",
105+workspace: path.join(tempRoot, "workspace-feishu-ou_sender"),
106+agentDir: path.join(tempRoot, "agent-feishu-ou_sender"),
107+},
108+],
109+},
110+bindings: [
111+{
112+agentId: "feishu-ou_sender",
113+match: {
114+channel: "feishu",
115+peer: { kind: "direct", id: "ou_sender" },
116+},
117+},
118+],
119+}),
120+afterWrite: { mode: "auto" },
121+});
122+expect(await pathExists(path.join(tempRoot, "workspace-feishu-ou_sender"))).toBe(true);
123+expect(await pathExists(path.join(tempRoot, "agent-feishu-ou_sender"))).toBe(true);
124+});
125+126+it("keeps the maxAgents limit before adding a missing binding", async () => {
127+const { runtime, replaceConfigFile } = createRuntime();
128+129+const result = await maybeCreateDynamicAgent({
130+cfg: {
131+agents: {
132+list: [
133+{
134+id: "feishu-ou_sender",
135+workspace: path.join(tempRoot, "existing-workspace"),
136+agentDir: path.join(tempRoot, "existing-agent"),
137+},
138+],
139+},
140+bindings: [],
141+} as OpenClawConfig,
142+ runtime,
143+senderOpenId: "ou_sender",
144+dynamicCfg: {
145+ ...createDynamicConfig(),
146+maxAgents: 1,
147+},
148+configWritesAllowed: true,
149+log: vi.fn(),
150+});
151+152+expect(result.created).toBe(false);
153+expect(replaceConfigFile).not.toHaveBeenCalled();
154+});
155+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。