慣性聚合 高效追讀感興趣之博客、新聞、科技資訊
閱原文 以慣性聚合開啟

推薦訂閱源

WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
T
The Blog of Author Tim Ferriss
Engineering at Meta
Engineering at Meta
Jina AI
Jina AI
Last Week in AI
Last Week in AI
I
InfoQ
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
人人都是产品经理
人人都是产品经理
MongoDB | Blog
MongoDB | Blog
The Cloudflare Blog
月光博客
月光博客
爱范儿
爱范儿
D
Docker
罗磊的独立博客
博客园 - 叶小钗
博客园 - 司徒正美

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 test: trim doctor command hotspots · openclaw/openclaw@c66f16a test: isolate agent auth and spawn hotspots · openclaw/openclaw@9285935 test: stabilize MCP startup disposal race · openclaw/openclaw@dd9d2eb test: merge browser contract server suites · openclaw/openclaw@5817a76 test: narrow ollama provider discovery setup · openclaw/openclaw@a0d9598 build: declare qa-lab aimock runtime dependency · openclaw/openclaw@24431e5 test: speed up safe-bins exec harness · openclaw/openclaw@ee856ab test: preserve tool helpers in embedded runner mocks · openclaw/openclaw@acd86a0 refactor: move memory embeddings into provider plugins · openclaw/openclaw@77e6e4c test: reuse system-run temp fixtures · openclaw/openclaw@7e9ff0f test: trim hotspot wait overhead · openclaw/openclaw@12a59b0 Check: avoid duplicate boundary prep · openclaw/openclaw@baf11b8 test: reduce hotspot fixture overhead · openclaw/openclaw@3a59edd feat(ui): overhaul settings and slash command UX (#67819) thanks @Bun… · openclaw/openclaw@2cfb660 QA Matrix: exit cleanly on failure · openclaw/openclaw@42805d2 QA Matrix: isolate scenario coverage · openclaw/openclaw@7e659e1 Matrix: refresh crypto bootstrap state · openclaw/openclaw@94081d8 QA Lab: add provider registry · openclaw/openclaw@bb7e982 Matrix: add plugin changelog · openclaw/openclaw@4acab55 test: trim more hotspot overhead · openclaw/openclaw@f485311
修e2e:束Open WebUI控探 · openclaw/openclaw@5eeaa56
vincentkoc · 2026-05-27 · via Recent Commits to openclaw:main

@@ -5,13 +5,23 @@ const email = process.env.OPENWEBUI_ADMIN_EMAIL ?? "";

55

const password = process.env.OPENWEBUI_ADMIN_PASSWORD ?? "";

66

const expectedNonce = process.env.OPENWEBUI_EXPECTED_NONCE ?? "";

77

const prompt = process.env.OPENWEBUI_PROMPT ?? "";

8-

const modelAttempts = Number.parseInt(process.env.OPENWEBUI_MODEL_ATTEMPTS ?? "72", 10);

9-

const modelRetryMs = Number.parseInt(process.env.OPENWEBUI_MODEL_RETRY_MS ?? "5000", 10);

10-

const fetchTimeoutMs = Number.parseInt(process.env.OPENWEBUI_FETCH_TIMEOUT_MS ?? "720000", 10);

8+

const modelAttempts = readPositiveInt(process.env.OPENWEBUI_MODEL_ATTEMPTS, 72);

9+

const modelRetryMs = readNonNegativeInt(process.env.OPENWEBUI_MODEL_RETRY_MS, 5000);

10+

const fetchTimeoutMs = readPositiveInt(process.env.OPENWEBUI_FETCH_TIMEOUT_MS, 720000);

11+

const controlTimeoutMs = readPositiveInt(

12+

process.env.OPENWEBUI_CONTROL_TIMEOUT_MS,

13+

Math.min(fetchTimeoutMs, 30000),

14+

);

15+

const chatTimeoutMs = readPositiveInt(process.env.OPENWEBUI_CHAT_TIMEOUT_MS, fetchTimeoutMs);

1116

const smokeMode =

1217

process.env.OPENWEBUI_SMOKE_MODE ?? process.env.OPENCLAW_OPENWEBUI_SMOKE_MODE ?? "chat";

131814-

setGlobalDispatcher(new Agent({ bodyTimeout: fetchTimeoutMs, headersTimeout: fetchTimeoutMs }));

19+

setGlobalDispatcher(

20+

new Agent({

21+

bodyTimeout: Math.max(controlTimeoutMs, chatTimeoutMs),

22+

headersTimeout: Math.max(controlTimeoutMs, chatTimeoutMs),

23+

}),

24+

);

15251626

if (!baseUrl || !email || !password || !expectedNonce || !prompt) {

1727

throw new Error("Missing required OPENWEBUI_* environment variables");

@@ -20,6 +30,41 @@ if (smokeMode !== "models" && smokeMode !== "chat") {

2030

throw new Error(`Unsupported OPENWEBUI_SMOKE_MODE: ${smokeMode}`);

2131

}

223233+

function readPositiveInt(raw, fallback) {

34+

const parsed = Number.parseInt(String(raw || ""), 10);

35+

return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;

36+

}

37+38+

function readNonNegativeInt(raw, fallback) {

39+

const parsed = Number.parseInt(String(raw || ""), 10);

40+

return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;

41+

}

42+43+

function createTimeoutError(label, timeoutMs) {

44+

const error = new Error(`${label} timed out after ${timeoutMs}ms`);

45+

error.code = "ETIMEDOUT";

46+

return error;

47+

}

48+49+

async function withRequestTimeout(label, timeoutMs, run) {

50+

const controller = new AbortController();

51+

const timeoutError = createTimeoutError(label, timeoutMs);

52+

const timer = setTimeout(() => {

53+

controller.abort(timeoutError);

54+

}, timeoutMs);

55+

timer.unref?.();

56+

try {

57+

return await run(controller.signal);

58+

} catch (error) {

59+

if (controller.signal.aborted) {

60+

throw timeoutError;

61+

}

62+

throw error;

63+

} finally {

64+

clearTimeout(timer);

65+

}

66+

}

67+2368

function getCookieHeader(res) {

2469

const raw = res.headers.get("set-cookie");

2570

if (!raw) {

@@ -49,6 +94,69 @@ function sleep(ms) {

4994

});

5095

}

519697+

async function fetchSignin() {

98+

return await withRequestTimeout("Open WebUI signin", controlTimeoutMs, async (signal) => {

99+

const response = await fetch(`${baseUrl}/api/v1/auths/signin`, {

100+

method: "POST",

101+

headers: { "content-type": "application/json" },

102+

body: JSON.stringify({ email, password }),

103+

signal,

104+

});

105+

if (!response.ok) {

106+

const body = await response.text();

107+

throw new Error(`signin failed: HTTP ${response.status} ${body}`);

108+

}

109+

return {

110+

cookie: getCookieHeader(response),

111+

json: await response.json(),

112+

};

113+

});

114+

}

115+116+

async function fetchModels(authHeaders, attempt) {

117+

return await withRequestTimeout(

118+

`Open WebUI models attempt ${attempt}`,

119+

controlTimeoutMs,

120+

async (signal) => {

121+

const response = await fetch(`${baseUrl}/api/models`, { headers: authHeaders, signal });

122+

if (!response.ok) {

123+

return {

124+

ok: false,

125+

status: response.status,

126+

text: await response.text(),

127+

};

128+

}

129+

return {

130+

json: await response.json(),

131+

ok: true,

132+

};

133+

},

134+

);

135+

}

136+137+

async function fetchChatCompletion(authHeaders, targetModel) {

138+

return await withRequestTimeout("Open WebUI chat completion", chatTimeoutMs, async (signal) => {

139+

const response = await fetch(`${baseUrl}/api/chat/completions`, {

140+

method: "POST",

141+

headers: {

142+

...authHeaders,

143+

"content-type": "application/json",

144+

},

145+

body: JSON.stringify({

146+

model: targetModel,

147+

messages: [{ role: "user", content: prompt }],

148+

}),

149+

signal,

150+

});

151+

if (!response.ok) {

152+

throw new Error(

153+

`/api/chat/completions failed: HTTP ${response.status} ${await response.text()}`,

154+

);

155+

}

156+

return await response.json();

157+

});

158+

}

159+52160

function extractModelIds(modelsJson) {

53161

const models = Array.isArray(modelsJson)

54162

? modelsJson

@@ -62,48 +170,37 @@ function extractModelIds(modelsJson) {

62170

.filter((value) => typeof value === "string");

63171

}

6417265-

const signinRes = await fetch(`${baseUrl}/api/v1/auths/signin`, {

66-

method: "POST",

67-

headers: { "content-type": "application/json" },

68-

body: JSON.stringify({ email, password }),

69-

});

70-

if (!signinRes.ok) {

71-

const body = await signinRes.text();

72-

throw new Error(`signin failed: HTTP ${signinRes.status} ${body}`);

73-

}

74-75-

const signinJson = await signinRes.json();

173+

const signin = await fetchSignin();

174+

const signinJson = signin.json;

76175

const token =

77176

signinJson?.token ?? signinJson?.access_token ?? signinJson?.jwt ?? signinJson?.data?.token ?? "";

78-

const cookie = getCookieHeader(signinRes);

79177

const authHeaders = {

80-

...buildAuthHeaders(token, cookie),

178+

...buildAuthHeaders(token, signin.cookie),

81179

accept: "application/json",

82180

};

8318184182

let modelIds = [];

85183

let targetModel = "";

86184

let lastModelsError = "";

87185

for (let attempt = 1; attempt <= modelAttempts; attempt += 1) {

88-

const modelsRes = await fetch(`${baseUrl}/api/models`, { headers: authHeaders }).catch(

89-

(error) => {

90-

lastModelsError = error instanceof Error ? error.message : String(error);

91-

return undefined;

92-

},

93-

);

94-

if (modelsRes?.ok) {

95-

const modelsJson = await modelsRes.json();

96-

modelIds = extractModelIds(modelsJson);

186+

const modelsResult = await fetchModels(authHeaders, attempt).catch((error) => {

187+

lastModelsError = error instanceof Error ? error.message : String(error);

188+

return undefined;

189+

});

190+

if (modelsResult?.ok) {

191+

modelIds = extractModelIds(modelsResult.json);

97192

targetModel =

98193

modelIds.find((id) => id === "openclaw/default") ?? modelIds.find((id) => id === "openclaw");

99194

if (targetModel) {

100195

break;

101196

}

102197

lastModelsError = `missing openclaw model: ${JSON.stringify(modelIds)}`;

103-

} else if (modelsRes) {

104-

lastModelsError = `HTTP ${modelsRes.status} ${await modelsRes.text()}`;

198+

} else if (modelsResult) {

199+

lastModelsError = `HTTP ${modelsResult.status} ${modelsResult.text}`;

200+

}

201+

if (attempt < modelAttempts) {

202+

await sleep(modelRetryMs);

105203

}

106-

await sleep(modelRetryMs);

107204

}

108205

if (!targetModel) {

109206

throw new Error(

@@ -115,21 +212,7 @@ if (smokeMode === "models") {

115212

process.exit(0);

116213

}

117214118-

const chatRes = await fetch(`${baseUrl}/api/chat/completions`, {

119-

method: "POST",

120-

headers: {

121-

...authHeaders,

122-

"content-type": "application/json",

123-

},

124-

body: JSON.stringify({

125-

model: targetModel,

126-

messages: [{ role: "user", content: prompt }],

127-

}),

128-

});

129-

if (!chatRes.ok) {

130-

throw new Error(`/api/chat/completions failed: HTTP ${chatRes.status} ${await chatRes.text()}`);

131-

}

132-

const chatJson = await chatRes.json();

215+

const chatJson = await fetchChatCompletion(authHeaders, targetModel);

133216

const reply =

134217

chatJson?.choices?.[0]?.message?.content ?? chatJson?.message?.content ?? chatJson?.content ?? "";

135218

if (typeof reply !== "string" || !reply.includes(expectedNonce)) {