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

推荐订阅源

L
LangChain Blog
S
SegmentFault 最新的问题
V
Visual Studio Blog
J
Java Code Geeks
宝玉的分享
宝玉的分享
美团技术团队
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
有赞技术团队
有赞技术团队
量子位
Martin Fowler
Martin Fowler
MyScale Blog
MyScale Blog
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
博客园 - 叶小钗
月光博客
月光博客
P
Proofpoint News Feed
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
Stack Overflow Blog
Stack Overflow Blog

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
fix(daemon): prefer system node for gateway install · ope...
steipete · 2026-05-04 · via Recent Commits to openclaw:main

@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";

2233

const fsMocks = vi.hoisted(() => ({

44

access: vi.fn(),

5+

realpath: vi.fn(),

56

}));

6778

vi.mock("node:fs/promises", async () => {

@@ -11,8 +12,10 @@ vi.mock("node:fs/promises", async () => {

1112

default: {

1213

...actual,

1314

access: fsMocks.access,

15+

realpath: fsMocks.realpath,

1416

},

1517

access: fsMocks.access,

18+

realpath: fsMocks.realpath,

1619

};

1720

});

1821

@@ -27,7 +30,12 @@ afterEach(() => {

2730

vi.resetAllMocks();

2831

});

293233+

function mockNodeRealpath(realpaths: Record<string, string> = {}) {

34+

fsMocks.realpath.mockImplementation(async (target: string) => realpaths[target] ?? target);

35+

}

36+3037

function mockNodePathPresent(...nodePaths: string[]) {

38+

mockNodeRealpath();

3139

fsMocks.access.mockImplementation(async (target: string) => {

3240

if (nodePaths.includes(target)) {

3341

return;

@@ -39,11 +47,119 @@ function mockNodePathPresent(...nodePaths: string[]) {

3947

describe("resolvePreferredNodePath", () => {

4048

const darwinNode = "/opt/homebrew/bin/node";

4149

const fnmNode = "/Users/test/.fnm/node-versions/v24.11.1/installation/bin/node";

50+

const linuxSystemNode = "/usr/bin/node";

51+

const nvmNode = "/home/test/.nvm/versions/node/v24.14.1/bin/node";

425243-

it("prefers execPath (version manager node) over system node", async () => {

53+

it("prefers supported system node over version-manager execPath", async () => {

4454

mockNodePathPresent(darwinNode);

455546-

const execFile = vi.fn().mockResolvedValue({ stdout: "24.11.1\n", stderr: "" });

56+

const execFile = vi

57+

.fn()

58+

.mockResolvedValueOnce({ stdout: "24.11.1\n", stderr: "" })

59+

.mockResolvedValueOnce({ stdout: "24.11.1\n", stderr: "" });

60+61+

const result = await resolvePreferredNodePath({

62+

env: {},

63+

runtime: "node",

64+

platform: "darwin",

65+

execFile,

66+

execPath: fnmNode,

67+

});

68+69+

expect(result).toBe(darwinNode);

70+

expect(execFile).toHaveBeenCalledTimes(2);

71+

});

72+73+

it("uses system node for Linux service installs instead of nvm execPath", async () => {

74+

mockNodePathPresent(linuxSystemNode);

75+76+

const execFile = vi

77+

.fn()

78+

.mockResolvedValueOnce({ stdout: "24.14.1\n", stderr: "" })

79+

.mockResolvedValueOnce({ stdout: "24.14.1\n", stderr: "" });

80+81+

const result = await resolvePreferredNodePath({

82+

env: {},

83+

runtime: "node",

84+

platform: "linux",

85+

execFile,

86+

execPath: nvmNode,

87+

});

88+89+

expect(result).toBe(linuxSystemNode);

90+

expect(execFile).toHaveBeenCalledTimes(2);

91+

});

92+93+

it("uses system node for Linux service installs instead of default fnm execPath", async () => {

94+

const linuxFnmNode = "/home/test/.local/share/fnm/aliases/default/bin/node";

95+

mockNodePathPresent(linuxSystemNode);

96+97+

const execFile = vi

98+

.fn()

99+

.mockResolvedValueOnce({ stdout: "24.14.1\n", stderr: "" })

100+

.mockResolvedValueOnce({ stdout: "24.14.1\n", stderr: "" });

101+102+

const result = await resolvePreferredNodePath({

103+

env: {},

104+

runtime: "node",

105+

platform: "linux",

106+

execFile,

107+

execPath: linuxFnmNode,

108+

});

109+110+

expect(result).toBe(linuxSystemNode);

111+

expect(execFile).toHaveBeenCalledTimes(2);

112+

});

113+114+

it("uses system node for macOS service installs instead of default fnm execPath", async () => {

115+

const darwinFnmNode = "/Users/test/Library/Application Support/fnm/aliases/default/bin/node";

116+

mockNodePathPresent(darwinNode);

117+118+

const execFile = vi

119+

.fn()

120+

.mockResolvedValueOnce({ stdout: "24.14.1\n", stderr: "" })

121+

.mockResolvedValueOnce({ stdout: "24.14.1\n", stderr: "" });

122+123+

const result = await resolvePreferredNodePath({

124+

env: {},

125+

runtime: "node",

126+

platform: "darwin",

127+

execFile,

128+

execPath: darwinFnmNode,

129+

});

130+131+

expect(result).toBe(darwinNode);

132+

expect(execFile).toHaveBeenCalledTimes(2);

133+

});

134+135+

it("uses Homebrew opt Node when a version-manager execPath is active", async () => {

136+

const homebrewOptNode = "/opt/homebrew/opt/node@22/bin/node";

137+

mockNodePathPresent(homebrewOptNode);

138+139+

const execFile = vi

140+

.fn()

141+

.mockResolvedValueOnce({ stdout: "24.11.1\n", stderr: "" })

142+

.mockResolvedValueOnce({ stdout: "22.17.0\n", stderr: "" });

143+144+

const result = await resolvePreferredNodePath({

145+

env: {},

146+

runtime: "node",

147+

platform: "darwin",

148+

execFile,

149+

execPath: fnmNode,

150+

});

151+152+

expect(result).toBe(homebrewOptNode);

153+

expect(execFile).toHaveBeenCalledTimes(2);

154+

});

155+156+

it("falls back to version-manager execPath when no supported system node exists", async () => {

157+

mockNodePathPresent(darwinNode);

158+159+

const execFile = vi

160+

.fn()

161+

.mockResolvedValueOnce({ stdout: "24.11.1\n", stderr: "" })

162+

.mockResolvedValueOnce({ stdout: "18.0.0\n", stderr: "" });

4716348164

const result = await resolvePreferredNodePath({

49165

env: {},

@@ -54,7 +170,7 @@ describe("resolvePreferredNodePath", () => {

54170

});

5517156172

expect(result).toBe(fnmNode);

57-

expect(execFile).toHaveBeenCalledTimes(1);

173+

expect(execFile).toHaveBeenCalledTimes(2);

58174

});

5917560176

it("falls back to system node when execPath version is unsupported", async () => {

@@ -248,6 +364,73 @@ describe("resolveSystemNodeInfo", () => {

248364

expect(result).toBeNull();

249365

});

250366367+

it("continues past an old system node to find a supported candidate", async () => {

368+

const homebrewOptNode = "/opt/homebrew/opt/node@22/bin/node";

369+

mockNodePathPresent(darwinNode, homebrewOptNode);

370+371+

const execFile = vi

372+

.fn()

373+

.mockResolvedValueOnce({ stdout: "18.0.0\n", stderr: "" })

374+

.mockResolvedValueOnce({ stdout: "22.17.0\n", stderr: "" });

375+376+

const result = await resolveSystemNodeInfo({

377+

env: {},

378+

platform: "darwin",

379+

execFile,

380+

});

381+382+

expect(result).toEqual({

383+

path: homebrewOptNode,

384+

version: "22.17.0",

385+

supported: true,

386+

});

387+

});

388+389+

it("skips system-node candidates that resolve into version-manager paths", async () => {

390+

const homebrewOptNode = "/opt/homebrew/opt/node@22/bin/node";

391+

mockNodePathPresent(darwinNode, homebrewOptNode);

392+

mockNodeRealpath({

393+

[darwinNode]: "/Users/test/.nvm/versions/node/v24.14.1/bin/node",

394+

[homebrewOptNode]: homebrewOptNode,

395+

});

396+397+

const execFile = vi.fn().mockResolvedValue({ stdout: "24.14.1\n", stderr: "" });

398+399+

const result = await resolveSystemNodeInfo({

400+

env: {},

401+

platform: "darwin",

402+

execFile,

403+

});

404+405+

expect(result).toEqual({

406+

path: homebrewOptNode,

407+

version: "24.14.1",

408+

supported: true,

409+

});

410+

expect(execFile).toHaveBeenCalledTimes(1);

411+

expect(execFile).toHaveBeenCalledWith(homebrewOptNode, ["-p", "process.versions.node"], {

412+

encoding: "utf8",

413+

});

414+

});

415+416+

it("returns null when every system-node candidate resolves into a version manager", async () => {

417+

mockNodePathPresent(darwinNode);

418+

mockNodeRealpath({

419+

[darwinNode]: "/Users/test/Library/Application Support/fnm/aliases/default/bin/node",

420+

});

421+422+

const execFile = vi.fn();

423+424+

const result = await resolveSystemNodeInfo({

425+

env: {},

426+

platform: "darwin",

427+

execFile,

428+

});

429+430+

expect(result).toBeNull();

431+

expect(execFile).not.toHaveBeenCalled();

432+

});

433+251434

it("renders a warning when system node is too old", () => {

252435

const warning = renderSystemNodeWarning(

253436

{