mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-03 05:31:33 +08:00
fix(openclaw): 兼容新版配置与 Node 门槛
This commit is contained in:
@@ -71,6 +71,7 @@ test('Hermes 配置页会暴露记忆结构化配置字段', () => {
|
||||
'hm-memory-user-char-limit',
|
||||
'hm-memory-nudge-interval',
|
||||
'hm-memory-flush-min-turns',
|
||||
'hm-memory-qmd-rerank',
|
||||
]) {
|
||||
assert.match(source, new RegExp(`id="${id}"`), `缺少 ${id}`)
|
||||
}
|
||||
@@ -243,6 +244,7 @@ test('Hermes 配置页会暴露 Tirith 安全扫描结构化配置字段', () =>
|
||||
'hm-security-tirith-path',
|
||||
'hm-security-tirith-timeout',
|
||||
'hm-security-tirith-fail-open',
|
||||
'hm-security-install-policy-json',
|
||||
]) {
|
||||
assert.match(source, new RegExp(`id="${id}"`), `缺少 ${id}`)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ test('Hermes 记忆配置读取会提供上游默认值', () => {
|
||||
userCharLimit: 1375,
|
||||
nudgeInterval: 10,
|
||||
flushMinTurns: 6,
|
||||
qmdRerank: true,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,6 +29,9 @@ test('Hermes 记忆配置读取会回显 YAML 中的记忆字段', () => {
|
||||
user_char_limit: 1800,
|
||||
nudge_interval: 12,
|
||||
flush_min_turns: 8,
|
||||
qmd: {
|
||||
rerank: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -37,6 +41,7 @@ test('Hermes 记忆配置读取会回显 YAML 中的记忆字段', () => {
|
||||
assert.equal(values.userCharLimit, 1800)
|
||||
assert.equal(values.nudgeInterval, 12)
|
||||
assert.equal(values.flushMinTurns, 8)
|
||||
assert.equal(values.qmdRerank, false)
|
||||
})
|
||||
|
||||
test('Hermes 记忆配置保存会保留无关 YAML 并写入 snake_case 字段', () => {
|
||||
@@ -47,6 +52,10 @@ test('Hermes 记忆配置保存会保留无关 YAML 并写入 snake_case 字段'
|
||||
provider: 'honcho',
|
||||
custom_flag: 'keep-me',
|
||||
flush_min_turns: 9,
|
||||
qmd: {
|
||||
provider: 'qmd',
|
||||
rerank: true,
|
||||
},
|
||||
},
|
||||
streaming: { enabled: true },
|
||||
}, {
|
||||
@@ -56,6 +65,7 @@ test('Hermes 记忆配置保存会保留无关 YAML 并写入 snake_case 字段'
|
||||
userCharLimit: '1500',
|
||||
nudgeInterval: '0',
|
||||
flushMinTurns: '7',
|
||||
qmdRerank: false,
|
||||
})
|
||||
|
||||
assert.deepEqual(next.model, { provider: 'anthropic' })
|
||||
@@ -66,6 +76,8 @@ test('Hermes 记忆配置保存会保留无关 YAML 并写入 snake_case 字段'
|
||||
assert.equal(next.memory.user_char_limit, 1500)
|
||||
assert.equal(next.memory.nudge_interval, 0)
|
||||
assert.equal(next.memory.flush_min_turns, 7)
|
||||
assert.equal(next.memory.qmd.rerank, false)
|
||||
assert.equal(next.memory.qmd.provider, 'qmd')
|
||||
assert.equal(next.memory.provider, 'honcho')
|
||||
assert.equal(next.memory.custom_flag, 'keep-me')
|
||||
})
|
||||
|
||||
@@ -14,6 +14,7 @@ test('Hermes 安全扫描配置读取会提供 Tirith 默认值', () => {
|
||||
tirithPath: 'tirith',
|
||||
tirithTimeout: 5,
|
||||
tirithFailOpen: true,
|
||||
installPolicyJson: '',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,6 +25,10 @@ test('Hermes 安全扫描配置读取会规范化已有值', () => {
|
||||
tirith_path: 'C:/tools/tirith.exe',
|
||||
tirith_timeout: 12,
|
||||
tirith_fail_open: false,
|
||||
installPolicy: {
|
||||
enabled: true,
|
||||
targets: ['skill', 'plugin'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -31,6 +36,10 @@ test('Hermes 安全扫描配置读取会规范化已有值', () => {
|
||||
assert.equal(values.tirithPath, 'C:/tools/tirith.exe')
|
||||
assert.equal(values.tirithTimeout, 12)
|
||||
assert.equal(values.tirithFailOpen, false)
|
||||
assert.deepEqual(JSON.parse(values.installPolicyJson), {
|
||||
enabled: true,
|
||||
targets: ['skill', 'plugin'],
|
||||
})
|
||||
})
|
||||
|
||||
test('Hermes 安全扫描配置保存会保留未知字段并写入 security.tirith', () => {
|
||||
@@ -39,6 +48,10 @@ test('Hermes 安全扫描配置保存会保留未知字段并写入 security.tir
|
||||
security: {
|
||||
allow_private_urls: false,
|
||||
website_blocklist: { enabled: true, domains: ['example.com'] },
|
||||
installPolicy: {
|
||||
enabled: false,
|
||||
targets: ['skill'],
|
||||
},
|
||||
custom_flag: 'keep-security',
|
||||
},
|
||||
terminal: { backend: 'docker' },
|
||||
@@ -47,6 +60,15 @@ test('Hermes 安全扫描配置保存会保留未知字段并写入 security.tir
|
||||
tirithPath: '~/bin/tirith',
|
||||
tirithTimeout: '9',
|
||||
tirithFailOpen: false,
|
||||
installPolicyJson: JSON.stringify({
|
||||
enabled: true,
|
||||
targets: ['skill', 'plugin'],
|
||||
exec: {
|
||||
source: 'exec',
|
||||
command: 'tirith',
|
||||
args: ['scan'],
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
assert.deepEqual(next.model, { provider: 'anthropic' })
|
||||
@@ -54,6 +76,15 @@ test('Hermes 安全扫描配置保存会保留未知字段并写入 security.tir
|
||||
assert.equal(next.security.allow_private_urls, false)
|
||||
assert.deepEqual(next.security.website_blocklist, { enabled: true, domains: ['example.com'] })
|
||||
assert.equal(next.security.custom_flag, 'keep-security')
|
||||
assert.deepEqual(next.security.installPolicy, {
|
||||
enabled: true,
|
||||
targets: ['skill', 'plugin'],
|
||||
exec: {
|
||||
source: 'exec',
|
||||
command: 'tirith',
|
||||
args: ['scan'],
|
||||
},
|
||||
})
|
||||
assert.equal(next.security.tirith_enabled, false)
|
||||
assert.equal(next.security.tirith_path, '~/bin/tirith')
|
||||
assert.equal(next.security.tirith_timeout, 9)
|
||||
@@ -69,4 +100,8 @@ test('Hermes 安全扫描配置保存会拒绝非法超时和空路径', () => {
|
||||
() => mergeHermesSecurityConfig({}, { tirithPath: '' }),
|
||||
/security\.tirith_path/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesSecurityConfig({}, { installPolicyJson: '[]' }),
|
||||
/security\.installPolicy/,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -20,13 +20,13 @@ test('Hermes Web 工具配置读取会回显 YAML 字段', () => {
|
||||
const values = buildHermesWebConfigValues({
|
||||
web: {
|
||||
backend: 'tavily',
|
||||
search_backend: 'searxng',
|
||||
search_backend: 'parallel-free',
|
||||
extract_backend: 'firecrawl',
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(values.webBackend, 'tavily')
|
||||
assert.equal(values.webSearchBackend, 'searxng')
|
||||
assert.equal(values.webSearchBackend, 'parallel-free')
|
||||
assert.equal(values.webExtractBackend, 'firecrawl')
|
||||
})
|
||||
|
||||
@@ -41,14 +41,14 @@ test('Hermes Web 工具配置保存会保留未知字段并写入上游结构',
|
||||
},
|
||||
streaming: { enabled: true },
|
||||
}, {
|
||||
webBackend: 'parallel',
|
||||
webBackend: 'parallel-free',
|
||||
webSearchBackend: 'exa',
|
||||
webExtractBackend: 'native',
|
||||
})
|
||||
|
||||
assert.deepEqual(next.model, { provider: 'anthropic' })
|
||||
assert.deepEqual(next.streaming, { enabled: true })
|
||||
assert.equal(next.web.backend, 'parallel')
|
||||
assert.equal(next.web.backend, 'parallel-free')
|
||||
assert.equal(next.web.search_backend, 'exa')
|
||||
assert.equal(next.web.extract_backend, 'native')
|
||||
assert.equal(next.web.custom_flag, 'keep-web')
|
||||
|
||||
39
tests/node-runtime-detection-policy.test.js
Normal file
39
tests/node-runtime-detection-policy.test.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const devApi = readFileSync(new URL('../scripts/dev-api.js', import.meta.url), 'utf8')
|
||||
const rustConfig = readFileSync(new URL('../src-tauri/src/commands/config.rs', import.meta.url), 'utf8')
|
||||
|
||||
test('web check_node prefers standalone bundled Node when available', () => {
|
||||
const start = devApi.indexOf('check_node() {')
|
||||
const end = devApi.indexOf('get_status_summary()', start)
|
||||
const fn = start >= 0 && end > start ? devApi.slice(start, end) : ''
|
||||
|
||||
assert.ok(fn, 'check_node handler must exist')
|
||||
assert.match(fn, /classifyCliSource\(cliPath\) === 'standalone'/)
|
||||
assert.match(fn, /standaloneBundledNodePath\(cliPath\)/)
|
||||
assert.match(fn, /detectedFrom: 'standalone-bundled'/)
|
||||
})
|
||||
|
||||
test('desktop check_node prefers standalone bundled Node before PATH lookup', () => {
|
||||
const start = rustConfig.indexOf('pub fn check_node()')
|
||||
const pathLookup = rustConfig.indexOf('let node_path = find_node_path', start)
|
||||
const bundledLookup = rustConfig.indexOf('standalone_bundled_node_bin(&cli_path)', start)
|
||||
|
||||
assert.ok(start >= 0, 'check_node must exist')
|
||||
assert.ok(bundledLookup > start, 'standalone bundled Node lookup must exist')
|
||||
assert.ok(pathLookup > bundledLookup, 'bundled Node lookup must run before PATH lookup')
|
||||
assert.match(rustConfig, /"standalone-bundled"/)
|
||||
})
|
||||
|
||||
test('Node 22.19 fallback is gated by OpenClaw 2026.6.5 or newer', () => {
|
||||
assert.match(devApi, /OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR = '2026\.6\.5'/)
|
||||
assert.match(devApi, /OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME = '>=22\.19\.0'/)
|
||||
assert.match(devApi, /versionGe\(baseVersion\(installedVersion\), OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR\)/)
|
||||
assert.doesNotMatch(devApi, /DEFAULT_OPENCLAW_NODE_REQUIREMENT/)
|
||||
|
||||
assert.match(rustConfig, /OPENCLAW_NODE_REQUIREMENT_VERSION_FLOOR: &str = "2026\.6\.5"/)
|
||||
assert.match(rustConfig, /OPENCLAW_NODE_REQUIREMENT_FOR_NEWER_RUNTIME: &str = ">=22\.19\.0"/)
|
||||
assert.match(rustConfig, /openclaw_version_requires_node_22_19/)
|
||||
})
|
||||
29
tests/patch-gateway-origins.test.js
Normal file
29
tests/patch-gateway-origins.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const devApi = readFileSync(new URL('../scripts/dev-api.js', import.meta.url), 'utf8')
|
||||
const pairing = readFileSync(new URL('../src-tauri/src/commands/pairing.rs', import.meta.url), 'utf8')
|
||||
|
||||
test('patchGatewayOrigins writes only allowedOrigins through merge path', () => {
|
||||
const start = devApi.indexOf('function patchGatewayOrigins()')
|
||||
const end = devApi.indexOf('function readOpenclawConfigOptional()', start)
|
||||
const fn = start >= 0 && end > start ? devApi.slice(start, end) : ''
|
||||
|
||||
assert.ok(fn, 'patchGatewayOrigins must exist')
|
||||
assert.match(fn, /只写入 allowedOrigins 增量/)
|
||||
assert.match(fn, /const partial = \{\s*gateway: \{\s*controlUi: \{\s*allowedOrigins: merged,/s)
|
||||
assert.match(fn, /mergeConfigsPreservingFields\(latest, partial\)/)
|
||||
assert.doesNotMatch(fn, /writeOpenclawConfigFile\(config\)/)
|
||||
})
|
||||
|
||||
test('patch_gateway_origins writes only allowedOrigins patch in Rust', () => {
|
||||
const start = pairing.indexOf('fn patch_gateway_origins()')
|
||||
const end = pairing.indexOf('#[tauri::command]', start)
|
||||
const fn = start >= 0 && end > start ? pairing.slice(start, end) : ''
|
||||
|
||||
assert.ok(fn, 'patch_gateway_origins must exist')
|
||||
assert.match(fn, /只写入 allowedOrigins 增量/)
|
||||
assert.match(fn, /let patch = serde_json::json!\(\{/)
|
||||
assert.match(fn, /save_openclaw_json\(&patch\)/)
|
||||
})
|
||||
Reference in New Issue
Block a user