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

推荐订阅源

雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
大猫的无限游戏
大猫的无限游戏
Google DeepMind News
Google DeepMind News
V
V2EX
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
Recent Announcements
Recent Announcements
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tailwind CSS Blog
美团技术团队
Martin Fowler
Martin Fowler
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks

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
feishu: honor config write policy for dynamic agents [AI]...
pgondhi987 · 2026-05-07 · via Recent Commits to openclaw:main

@@ -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+

});