From 9af1b7179d7e2e6778f4007b42cafe2f23f982d4 Mon Sep 17 00:00:00 2001 From: NingWei Date: Tue, 7 Jul 2026 10:27:27 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/case/integration/playwright-flow.js | 449 ++++++++++++++++++++++- 1 file changed, 446 insertions(+), 3 deletions(-) diff --git a/test/case/integration/playwright-flow.js b/test/case/integration/playwright-flow.js index 8850d74..896e64f 100644 --- a/test/case/integration/playwright-flow.js +++ b/test/case/integration/playwright-flow.js @@ -59,6 +59,9 @@ const PACKAGE_INSTALLS = [ { manager: "uv", packageName: "pyfiglet", version: "1.0.2", spec: "pyfiglet@1.0.2" }, { manager: "pip", packageName: "humanize", version: "4.9.0", spec: "humanize@4.9.0" } ]; +const MANAGEMENT_WEB_SENDER_ID = "aios-management-web"; +const DEFAULT_EXTERNAL_SENDER_ID = "aios-integration-test"; +const SENDER_ISOLATION_SENDER_ID = "aios-integration-isolated"; const SCHEDULED_TASKS = [ { name: "llm-sync", key: "llm_sync" }, @@ -1274,6 +1277,30 @@ function assertPackageNamesInText(text, context) { } } +function assertTextDoesNotContain(text, forbiddenText, context) { + if (!forbiddenText) { + return; + } + assert.equal( + String(text || "").includes(forbiddenText), + false, + `${context} should not include ${JSON.stringify(forbiddenText)}` + ); +} + +function collectResultReplyText(result) { + return [ + result?.reply?.text, + ...(Array.isArray(result?.observedReplyMessages) + ? result.observedReplyMessages.map((message) => message?.text) + : []) + ].filter(Boolean).join("\n"); +} + +function createSenderIsolationMarker(label) { + return `${label}-${Date.now()}-${randomBytes(6).toString("hex")}`; +} + function readReplyMessageText(message) { return String(message?.payload?.text || ""); } @@ -1597,6 +1624,8 @@ async function runExternalAgentConversation({ timeoutMs, expectedText, matcher: customMatcher, + senderId = DEFAULT_EXTERNAL_SENDER_ID, + senderName = "AIOS Integration Test", clientIdPrefix = "aios-flow-external" }) { const mqttConfig = readMqttConfig(envJsonPath); @@ -1679,8 +1708,8 @@ async function runExternalAgentConversation({ }); await publishMqtt(client, inboundTopic, { - senderId: "aios-integration-test", - senderName: "AIOS Integration Test", + senderId, + senderName, sessionId: session.sessionId, disableBlockStreaming: false, text @@ -1692,6 +1721,7 @@ async function runExternalAgentConversation({ ok: Boolean(matcher?.(replyMessage)), responseTimeMs: Date.now() - startedAt, sessionId: session.sessionId, + senderId, session, topics: { inboundTopic, @@ -1713,6 +1743,159 @@ async function runExternalAgentConversation({ } } +async function agentPageWriteSenderIsolationMarker(page, baseUrl, marker, timeoutMs) { + const expectedText = `SENDER_A_MARKER=${marker}`; + const senderMemoryPath = `${MANAGEMENT_WEB_SENDER_ID}/memory/YYYY-MM-DD.md`; + return await runAgentConversationTestWithPlaywright( + page, + baseUrl, + AGENT_ID, + [ + "Use only the current senderId private persistent memory files.", + "Do not read or write any other senderId directory.", + "Do not create or use workspace-root MEMORY.md, workspace-root memory/, or memory// paths.", + `For senderId ${MANAGEMENT_WEB_SENDER_ID}, append this exact line to ${senderMemoryPath}, replacing YYYY-MM-DD with today's date: sender-isolation-marker=${marker}`, + `Reply exactly: ${expectedText}`, + "Do not include any other text." + ].join("\n"), + timeoutMs, + { + expectedText, + maxReplyLength: 1200 + } + ); +} + +async function agentPageRecallSenderIsolationMarker(page, baseUrl, marker, forbiddenMarker, timeoutMs) { + const expectedText = `SENDER_A_MARKER=${marker}`; + const result = await runAgentConversationTestWithPlaywright( + page, + baseUrl, + AGENT_ID, + [ + "Use only the current senderId private persistent memory files.", + "Do not read shared files, root memory files, memory// paths, or any other senderId directory.", + `Read only ${MANAGEMENT_WEB_SENDER_ID}/memory/ and ${MANAGEMENT_WEB_SENDER_ID}/MEMORY.md if it exists.`, + `If that sender memory contains sender-isolation-marker=${marker}, reply exactly: ${expectedText}`, + "If it does not exist, reply exactly: NO_SENDER_MARKER", + "Do not include any other text." + ].join("\n"), + timeoutMs, + { + expectedText, + maxReplyLength: 1200 + } + ); + assertTextDoesNotContain( + collectResultReplyText(result), + forbiddenMarker, + "management sender isolation recall reply" + ); + assertTextDoesNotContain( + collectResultReplyText(result), + "NO_SENDER_MARKER", + "management sender isolation recall reply" + ); + return result; +} + +async function externalSenderIsolationWriteAndCheck({ + baseUrl, + serviceToken, + envJsonPath, + senderId, + marker, + forbiddenMarker, + timeoutMs +}) { + const expectedText = `NO_PRIOR_MARKER; SENDER_B_MARKER=${marker}`; + const senderMemoryPath = `${senderId}/memory/YYYY-MM-DD.md`; + const result = await runExternalAgentConversation({ + baseUrl, + serviceToken, + envJsonPath, + senderId, + senderName: "AIOS Integration Isolated Sender", + clientIdPrefix: "aios-flow-sender-isolation", + text: [ + "Use only the current senderId private persistent memory files.", + "Do not read shared files, root memory files, or any other senderId directory.", + "Do not create or use workspace-root MEMORY.md, workspace-root memory/, or memory// paths.", + `For senderId ${senderId}, inspect only ${senderId}/memory/ and ${senderId}/MEMORY.md if it exists.`, + "If that sender memory already contains any sender-isolation-marker line, reply exactly: UNEXPECTED_PRIOR_MARKER", + `If no sender-isolation-marker exists, append this exact line to ${senderMemoryPath}, replacing YYYY-MM-DD with today's date: sender-isolation-marker=${marker}`, + `Then reply exactly: ${expectedText}`, + "Do not include any other text." + ].join("\n"), + timeoutMs, + expectedText + }); + + assert.equal( + result.ok, + true, + `isolated external sender should report no prior marker and write its own marker: ${JSON.stringify(result.reply)}` + ); + assertTextDoesNotContain( + collectResultReplyText(result), + forbiddenMarker, + "isolated external sender write reply" + ); + assertTextDoesNotContain( + collectResultReplyText(result), + "UNEXPECTED_PRIOR_MARKER", + "isolated external sender write reply" + ); + return result; +} + +async function externalSenderIsolationRecall({ + baseUrl, + serviceToken, + envJsonPath, + senderId, + marker, + forbiddenMarker, + timeoutMs +}) { + const expectedText = `SENDER_B_MARKER=${marker}`; + const result = await runExternalAgentConversation({ + baseUrl, + serviceToken, + envJsonPath, + senderId, + senderName: "AIOS Integration Isolated Sender", + clientIdPrefix: "aios-flow-sender-isolation-recall", + text: [ + "Use only the current senderId private persistent memory files.", + "Do not read shared files, root memory files, memory// paths, or any other senderId directory.", + `Read only ${senderId}/memory/ and ${senderId}/MEMORY.md if it exists.`, + `If that sender memory contains sender-isolation-marker=${marker}, reply exactly: ${expectedText}`, + "If it does not exist, reply exactly: NO_SENDER_MARKER", + "Do not include any other text." + ].join("\n"), + timeoutMs, + expectedText + }); + + assert.equal( + result.ok, + true, + `isolated external sender should recall its own marker: ${JSON.stringify(result.reply)}` + ); + assertTextDoesNotContain( + collectResultReplyText(result), + forbiddenMarker, + "isolated external sender recall reply" + ); + assertTextDoesNotContain( + collectResultReplyText(result), + "NO_SENDER_MARKER", + "isolated external sender recall reply" + ); + return result; +} + async function externalAgentOntologyVersion(baseUrl, serviceToken, envJsonPath, timeoutMs) { const result = await runExternalAgentConversation({ baseUrl, @@ -2647,6 +2830,245 @@ async function verifyInstalledPackagesPersisted() { return await verifyInstalledPackages("package persistence verification"); } +async function verifySenderMemoryLayout(targets, { + managementSenderId, + externalSenderId, + managementMarker, + externalMarker +}, timeoutMs = 60000) { + assert.ok(targets.length > 0, "restart targets are required to verify sender memory layout"); + const container = targets[0]; + const workspace = `/var/aios/.openclaw/workspaces/${AGENT_ID}`; + const command = [ + "set -euo pipefail", + `WORKSPACE=${JSON.stringify(workspace)}`, + `MANAGEMENT_SENDER=${JSON.stringify(managementSenderId)}`, + `EXTERNAL_SENDER=${JSON.stringify(externalSenderId)}`, + `MANAGEMENT_MARKER=${JSON.stringify(managementMarker)}`, + `EXTERNAL_MARKER=${JSON.stringify(externalMarker)}`, + "export WORKSPACE MANAGEMENT_SENDER EXTERNAL_SENDER MANAGEMENT_MARKER EXTERNAL_MARKER", + "node <<'NODE'", + "const fs = require('node:fs');", + "const path = require('node:path');", + "const workspace = process.env.WORKSPACE;", + "const managementSender = process.env.MANAGEMENT_SENDER;", + "const externalSender = process.env.EXTERNAL_SENDER;", + "const managementMarker = process.env.MANAGEMENT_MARKER;", + "const externalMarker = process.env.EXTERNAL_MARKER;", + "function toPosixRelative(file) {", + " return path.relative(workspace, file).split(path.sep).join('/');", + "}", + "function listFiles(root) {", + " if (!fs.existsSync(root)) {", + " return [];", + " }", + " const out = [];", + " for (const entry of fs.readdirSync(root, { withFileTypes: true })) {", + " const full = path.join(root, entry.name);", + " if (entry.isDirectory()) {", + " out.push(...listFiles(full));", + " } else if (entry.isFile()) {", + " out.push(full);", + " }", + " }", + " return out.sort();", + "}", + "function readSenderFiles(sender) {", + " const senderRoot = path.join(workspace, sender);", + " const files = [", + " ...listFiles(path.join(senderRoot, 'memory')),", + " ...(fs.existsSync(path.join(senderRoot, 'MEMORY.md')) ? [path.join(senderRoot, 'MEMORY.md')] : [])", + " ];", + " return {", + " sender,", + " senderRoot,", + " memoryDir: path.join(senderRoot, 'memory'),", + " files: files.map((file) => ({", + " file,", + " relativePath: toPosixRelative(file),", + " text: fs.readFileSync(file, 'utf8')", + " }))", + " };", + "}", + "function contains(senderFiles, marker) {", + " return senderFiles.files.some((file) => file.text.includes(marker));", + "}", + "function assertSenderFiles(senderFiles) {", + " if (!fs.existsSync(senderFiles.senderRoot)) {", + " throw new Error(`missing sender root: ${senderFiles.senderRoot}`);", + " }", + " if (!fs.existsSync(senderFiles.memoryDir)) {", + " throw new Error(`missing sender memory dir: ${senderFiles.memoryDir}`);", + " }", + " if (senderFiles.files.length === 0) {", + " throw new Error(`missing sender memory files: ${senderFiles.memoryDir}`);", + " }", + "}", + "if (!fs.existsSync(workspace)) {", + " throw new Error(`missing workspace: ${workspace}`);", + "}", + "const rootMemoryMd = path.join(workspace, 'MEMORY.md');", + "const rootMemoryDir = path.join(workspace, 'memory');", + "if (fs.existsSync(rootMemoryMd) || fs.existsSync(rootMemoryDir)) {", + " throw new Error(`root memory must stay disabled: MEMORY.md=${fs.existsSync(rootMemoryMd)} memory=${fs.existsSync(rootMemoryDir)}`);", + "}", + "const managementFiles = readSenderFiles(managementSender);", + "const externalFiles = readSenderFiles(externalSender);", + "assertSenderFiles(managementFiles);", + "assertSenderFiles(externalFiles);", + "if (!contains(managementFiles, managementMarker)) {", + " throw new Error(`management sender memory does not contain marker ${managementMarker}`);", + "}", + "if (!contains(externalFiles, externalMarker)) {", + " throw new Error(`external sender memory does not contain marker ${externalMarker}`);", + "}", + "if (contains(managementFiles, externalMarker)) {", + " throw new Error(`management sender memory leaked external marker ${externalMarker}`);", + "}", + "if (contains(externalFiles, managementMarker)) {", + " throw new Error(`external sender memory leaked management marker ${managementMarker}`);", + "}", + "console.log(JSON.stringify({", + " workspace,", + " rootMemory: {", + " memoryMdExists: fs.existsSync(rootMemoryMd),", + " memoryDirExists: fs.existsSync(rootMemoryDir)", + " },", + " senders: {", + " [managementSender]: {", + " memoryDir: toPosixRelative(managementFiles.memoryDir),", + " files: managementFiles.files.map((file) => file.relativePath),", + " containsOwnMarker: contains(managementFiles, managementMarker),", + " containsOtherMarker: contains(managementFiles, externalMarker)", + " },", + " [externalSender]: {", + " memoryDir: toPosixRelative(externalFiles.memoryDir),", + " files: externalFiles.files.map((file) => file.relativePath),", + " containsOwnMarker: contains(externalFiles, externalMarker),", + " containsOtherMarker: contains(externalFiles, managementMarker)", + " }", + " },", + " shared: {", + " skillsRootExists: fs.existsSync(path.join(workspace, 'skills')),", + " learningsRootExists: fs.existsSync(path.join(workspace, '.learnings'))", + " }", + "}));", + "NODE" + ].join("\n"); + + const startedAt = Date.now(); + let lastError = ""; + while (Date.now() - startedAt < timeoutMs) { + try { + const result = await runRequired( + "docker", + ["exec", "-u", "aios-agent", container, "/bin/bash", "-lc", command], + { timeout: 30000 } + ); + const probe = JSON.parse(result.stdout.split(/\r?\n/).filter(Boolean).at(-1) || "{}"); + return { + container, + responseTimeMs: Date.now() - startedAt, + ...probe + }; + } catch (error) { + lastError = error instanceof Error ? error.message : String(error); + await sleep(1000); + } + } + + throw new Error(`sender memory layout did not pass within ${timeoutMs}ms: ${lastError}`); +} + +async function verifySenderMemoryIsolation({ + page, + baseUrl, + serviceToken, + envJsonPath, + targets, + timeoutMs +}) { + const managementSenderId = MANAGEMENT_WEB_SENDER_ID; + const externalSenderId = SENDER_ISOLATION_SENDER_ID; + const managementMarker = createSenderIsolationMarker("sender-a"); + const externalMarker = createSenderIsolationMarker("sender-b"); + const startedAt = Date.now(); + + const managementWrite = await agentPageWriteSenderIsolationMarker( + page, + baseUrl, + managementMarker, + timeoutMs + ); + const externalWrite = await externalSenderIsolationWriteAndCheck({ + baseUrl, + serviceToken, + envJsonPath, + senderId: externalSenderId, + marker: externalMarker, + forbiddenMarker: managementMarker, + timeoutMs + }); + const externalRecall = await externalSenderIsolationRecall({ + baseUrl, + serviceToken, + envJsonPath, + senderId: externalSenderId, + marker: externalMarker, + forbiddenMarker: managementMarker, + timeoutMs + }); + const managementRecall = await agentPageRecallSenderIsolationMarker( + page, + baseUrl, + managementMarker, + externalMarker, + timeoutMs + ); + const layout = await verifySenderMemoryLayout(targets, { + managementSenderId, + externalSenderId, + managementMarker, + externalMarker + }); + + return { + ok: true, + responseTimeMs: Date.now() - startedAt, + managementSenderId, + externalSenderId, + markers: { + managementMarker, + externalMarker + }, + conversations: { + managementWrite: { + responseTimeMs: managementWrite.responseTimeMs, + sessionId: managementWrite.sessionId, + reply: managementWrite.reply + }, + externalWrite: { + responseTimeMs: externalWrite.responseTimeMs, + sessionId: externalWrite.sessionId, + senderId: externalWrite.senderId, + reply: externalWrite.reply + }, + externalRecall: { + responseTimeMs: externalRecall.responseTimeMs, + sessionId: externalRecall.sessionId, + senderId: externalRecall.senderId, + reply: externalRecall.reply + }, + managementRecall: { + responseTimeMs: managementRecall.responseTimeMs, + sessionId: managementRecall.sessionId, + reply: managementRecall.reply + } + }, + layout + }; +} + async function main() { const baseUrl = readText(runtimeConfig.baseUrl, readText(flowConfig.baseUrl, DEFAULT_BASE_URL)); const envJsonPath = path.resolve(readText(runtimeConfig.envJson, resolveConfigPath(flowConfig.envJson, ".env.json"))); @@ -2691,7 +3113,8 @@ async function main() { agentResponseTimeMs: null, packageInstallTimeMs: null, restartTimeMs: null, - packageMemoryResponseTimeMs: null + packageMemoryResponseTimeMs: null, + senderIsolationResponseTimeMs: null }, summary: { startup_time_ms: initialStartupTimeMs, @@ -2702,6 +3125,7 @@ async function main() { package_install_time_ms: null, restart_time_ms: null, package_memory_response_time_ms: null, + sender_isolation_response_time_ms: null, version_info: null }, versionInfo: null, @@ -3158,6 +3582,25 @@ async function main() { reply: report.packageMemory.reply }); + report.senderMemoryIsolation = await verifySenderMemoryIsolation({ + page, + baseUrl, + serviceToken, + envJsonPath, + targets: restartTargets, + timeoutMs: readTimeout("agentMemory", 240000) + }); + report.timings.senderIsolationResponseTimeMs = report.senderMemoryIsolation.responseTimeMs; + report.summary.sender_isolation_response_time_ms = report.senderMemoryIsolation.responseTimeMs; + addStep(report, "15.verify-sender-memory-isolation", true, { + responseTimeMs: report.senderMemoryIsolation.responseTimeMs, + managementSenderId: report.senderMemoryIsolation.managementSenderId, + externalSenderId: report.senderMemoryIsolation.externalSenderId, + markers: report.senderMemoryIsolation.markers, + conversations: report.senderMemoryIsolation.conversations, + layout: report.senderMemoryIsolation.layout + }); + report.ok = true; } catch (error) { report.ok = false; -- Gitee From beec41d4ba41237ec23033155dc29f95cbf0c051 Mon Sep 17 00:00:00 2001 From: NingWei Date: Wed, 8 Jul 2026 10:54:32 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E9=81=BF=E5=85=8Dskill=5Fworksop=E7=9A=84?= =?UTF-8?q?=E5=B9=B2=E6=89=B0=EF=BC=8C=E7=9B=B4=E6=8E=A5=E7=94=A8skill=5Fc?= =?UTF-8?q?reator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/assets/config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/assets/config.json b/docker/assets/config.json index 22bb118..e011fde 100644 --- a/docker/assets/config.json +++ b/docker/assets/config.json @@ -48,6 +48,9 @@ "dmScope": "per-channel-peer" }, "tools": { + "deny": [ + "skill_workshop" + ], "exec": { "mode": "full" } -- Gitee From ccf192e68aa693e24060172f7a951915b2eb01d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=81=E4=BC=9F?= <55093136@qq.com> Date: Fri, 17 Jul 2026 22:20:07 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E5=8D=87=E7=BA=A7oc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/BUILD.md | 28 +- docker/Dockerfile | 12 +- docker/assets/scripts/kernel-startup.sh | 57 +- docker/build-x64.sh | 256 ++++++++ docker/docs/environment-variables.md | 3 + .../openclaw-2026.7.1-model-comparison.md | 116 ++++ ...openclaw-2026.7.1-regression-comparison.md | 95 +++ .../openclaw-2026.7.1-validation-report.md | 64 ++ docker/docs/openclaw-upgrade-runbook.md | 66 ++ .../internal/kernel/vendor/npm/attention.md | 4 +- .../aios-mqtt-channel/package-lock.json | 597 ++++++++++-------- .../aios-mqtt-channel/package.json | 2 +- test/case/integration/playwright-flow.js | 166 ++--- .../upgrade/openclaw-upgrade-contract.test.js | 57 ++ test/package.json | 2 +- 15 files changed, 1180 insertions(+), 345 deletions(-) create mode 100755 docker/build-x64.sh create mode 100644 docker/docs/openclaw-2026.7.1-model-comparison.md create mode 100644 docker/docs/openclaw-2026.7.1-regression-comparison.md create mode 100644 docker/docs/openclaw-2026.7.1-validation-report.md create mode 100644 docker/docs/openclaw-upgrade-runbook.md create mode 100644 test/case/upgrade/openclaw-upgrade-contract.test.js diff --git a/docker/BUILD.md b/docker/BUILD.md index 60f4cb2..7ba18b5 100644 --- a/docker/BUILD.md +++ b/docker/BUILD.md @@ -10,7 +10,7 @@ Docker 资产统一放在 `docker/assets`;`docker/internal` 只保留构建脚本和离线 vendor 资源。 -`tool-builder` 阶段会优先使用 `docker/internal/kernel/vendor/npm` 中匹配包名和版本的本地 `.tgz`。例如可用 `npm pack openclaw@2026.6.10 --pack-destination docker/internal/kernel/vendor/npm` 预先放入本地包;未命中时才从 `AIOS_BUILD_NPM_REGISTRY` 下载。 +`tool-builder` 阶段会优先使用 `docker/internal/kernel/vendor/npm` 中匹配包名和版本的本地 `.tgz`。例如可用 `npm pack openclaw@2026.7.1 --pack-destination docker/internal/kernel/vendor/npm` 预先放入本地包;未命中时才从 `AIOS_BUILD_NPM_REGISTRY` 下载。 ## 推荐脚本 @@ -26,6 +26,26 @@ X64: powershell -ExecutionPolicy Bypass -File .\docker\build-x64.ps1 ``` +Linux X64(自动下载缺失的 vendor 文件后构建): + +```bash +chmod +x docker/build-x64.sh +docker/build-x64.sh +``` + +该脚本的下载步骤也在临时 Docker 容器中执行,因此宿主机无需安装 Node.js、npm 或 curl。已存在的 vendor 文件会被跳过,可安全地重复运行。常用参数: + +```bash +docker/build-x64.sh --version 20260601.01 --pull +docker/build-x64.sh --image-name registry.example.com/aios --image-tag 20260601.01 +``` + +如果 npm 包来自需要认证的 registry,可通过环境变量传入令牌: + +```bash +NODE_AUTH_TOKEN=*** docker/build-x64.sh --npm-registry https://registry.example.com +``` + 两个脚本默认使用 `local` tag,也支持指定版本: ```bash @@ -60,6 +80,12 @@ docker build ` ## 测试 +OpenClaw 版本、Node 版本、迁移顺序、插件所有权和防重启风暴契约: + +```bash +npm --prefix test run test:upgrade-contract +``` + Docker 容器测试放在根目录 `test`,目录按职责拆分为 `common/`、`prepare/`、`case/`、`suite/`。常规入口优先使用 suite: ```powershell diff --git a/docker/Dockerfile b/docker/Dockerfile index 959c766..1d34959 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,13 @@ # syntax=docker/dockerfile:1.7 -ARG OPENCLAW_VERSION=2026.6.10 +ARG OPENCLAW_VERSION=2026.7.1 +ARG NODE_VERSION=24.18.0 ARG AEDES_VERSION=1.1.0 ARG AIOS_APPS_INVOKE_CLI_VERSION=1.0.3 ARG AIOS_MANAGEMENT_WEB_NPM_VERSION=1.0.17 ARG AIOS_BUILD_NPM_REGISTRY=https://registry.npmjs.org -FROM node:24-bookworm-slim AS runtime-base +FROM node:${NODE_VERSION}-bookworm-slim AS runtime-base ARG TARGETARCH ARG UV_VERSION=0.11.24 @@ -51,6 +52,8 @@ ENV FFMPEG_PATH=/usr/bin/ffmpeg ENV FFPROBE_PATH=/usr/bin/ffprobe ENV AGENT_BROWSER_EXECUTABLE_PATH= ENV OPENCLAW_COMMAND=/opt/aios-toolchain/bin/openclaw +ENV OPENCLAW_NO_RESPAWN=1 +ENV NODE_COMPILE_CACHE=/var/aios/.openclaw/cache/node-compile ENV QMD_COMMAND=/opt/aios-toolchain/bin/qmd ENV QMD_EMBED_MODEL=/opt/aios-vendor/qmd-models/embeddinggemma-300M-Q8_0.gguf ENV QMD_RERANK_MODEL=/opt/aios-vendor/qmd-models/qwen3-reranker-0.6b-q8_0.gguf @@ -305,7 +308,7 @@ RUN set -eux; \ 'delay_seconds="${AIOS_SEED_SKILL_INSTALL_RETRY_DELAY_SECONDS:-20}"' \ 'attempt=1' \ 'while true; do' \ - ' if XDG_CACHE_HOME="${cache_home}" XDG_CONFIG_HOME="${config_home}" openclaw skills install "${skill}" --global --force "$@"; then' \ + ' if XDG_CACHE_HOME="${cache_home}" XDG_CONFIG_HOME="${config_home}" openclaw skills install "${skill}" --global --force --acknowledge-clawhub-risk "$@"; then' \ ' exit 0' \ ' fi' \ ' if (( attempt >= max_attempts )); then' \ @@ -376,7 +379,7 @@ RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ RUN set -eux; \ rm -rf "${XDG_CACHE_HOME}" "${HOME}/.npm" -FROM node:24-bookworm-slim AS web-installer +FROM node:${NODE_VERSION}-bookworm-slim AS web-installer ARG AIOS_MANAGEMENT_WEB_NPM_VERSION ARG AIOS_BUILD_NPM_REGISTRY=https://registry.npmjs.org @@ -576,6 +579,7 @@ RUN set -eux; \ seed_mqtt_project="/opt/aios-seed/home/.openclaw/npm/projects/aios-mqtt-channel"; \ seed_skill_parent="/opt/aios-seed/home/.openclaw/skills"; \ test -d "${seed_mqtt_project}/node_modules/aios-mqtt-channel"; \ + chown -R root:"${AIOS_OPENCLAW_GROUP}" "${seed_mqtt_project}"; \ chown "${AIOS_SERVICE_USER}:${AIOS_OPENCLAW_GROUP}" \ /opt/aios-seed \ /opt/aios-seed/home \ diff --git a/docker/assets/scripts/kernel-startup.sh b/docker/assets/scripts/kernel-startup.sh index 6953128..382fd60 100644 --- a/docker/assets/scripts/kernel-startup.sh +++ b/docker/assets/scripts/kernel-startup.sh @@ -55,6 +55,10 @@ OPENCLAW_COMMAND="${OPENCLAW_COMMAND:-${AIOS_TOOLCHAIN_DIR}/bin/openclaw}" DOCTOR_COMMAND="${DOCTOR_COMMAND:-${AIOS_TOOLCHAIN_DIR}/bin/doctor}" OPENCLAW_PID_FILE="${AIOS_LOG_DIR}/openclaw.pid" OPENCLAW_RESTART_DELAY_SECONDS="${OPENCLAW_RESTART_DELAY_SECONDS:-2}" +OPENCLAW_FATAL_EXIT_CODE="${OPENCLAW_FATAL_EXIT_CODE:-78}" +OPENCLAW_MAX_RAPID_RESTARTS="${OPENCLAW_MAX_RAPID_RESTARTS:-5}" +OPENCLAW_RAPID_RESTART_WINDOW_SECONDS="${OPENCLAW_RAPID_RESTART_WINDOW_SECONDS:-60}" +OPENCLAW_MIGRATION_STAMP="${AIOS_OPENCLAW_HOME}/state/aios-openclaw-migrated.version" RUNTIME_ENV_KEYS=() UV_PACKAGE_CHANGED=false UV_PACKAGE_NEEDS_REWRITE=false @@ -74,7 +78,7 @@ TEMPLATE_PERMISSION_NORMALIZE_STAMP="${PERMISSION_NORMALIZE_STAMP_ROOT}/template AIOS_FORCE_FULL_PERMISSION_NORMALIZE="${AIOS_FORCE_FULL_PERMISSION_NORMALIZE:-false}" AIOS_SYNC_SEED_UV_PACKAGE="${AIOS_SYNC_SEED_UV_PACKAGE:-false}" AIOS_LINK_SEED_SKILLS="${AIOS_LINK_SEED_SKILLS:-true}" -STARTUP_STEP_TOTAL=30 +STARTUP_STEP_TOTAL=31 STARTUP_STEP_INDEX=0 STARTUP_ACTIVE_STEP_INDEX=0 STARTUP_ACTIVE_STEP_NAME="" @@ -2213,6 +2217,7 @@ patch_openclaw_config() { | .memory.qmd.includeDefaultMemory = false | .memory.qmd.update.startup = "idle" | .memory.qmd.limits.timeoutMs = 120000 + | .plugins.bundledDiscovery = (.plugins.bundledDiscovery // "compat") | .plugins.allow = ((.plugins.allow // []) + ["aios-mqtt-channel"] | unique) | .plugins.load.paths = ((.plugins.load.paths // []) + [$seedMqttPluginPath] | unique) | .plugins.entries["aios-mqtt-channel"].enabled = true @@ -2230,6 +2235,36 @@ patch_openclaw_config() { ensure_shared_runtime_file_mode "$config_path" } +migrate_openclaw_state() { + local current_version previous_version migration_log + + current_version="$(run_with_runtime_env "$AIOS_OPENCLAW_USER" "$OPENCLAW_COMMAND" --version)" + previous_version="$(cat "$OPENCLAW_MIGRATION_STAMP" 2>/dev/null || true)" + if [[ -n "$current_version" && "$current_version" == "$previous_version" ]]; then + log "skipping OpenClaw state migration; version unchanged: ${current_version}" + return 0 + fi + + migration_log="$(mktemp "${AIOS_TMP_DIR}/openclaw-migration.XXXXXX.log")" + if ! run_with_runtime_env "$AIOS_OPENCLAW_USER" "$OPENCLAW_COMMAND" doctor --non-interactive >"$migration_log" 2>&1; then + sed 's/^/[openclaw-migration] /' "$migration_log" >&2 + rm -f "$migration_log" + startup_step_error "OpenClaw state migration failed for ${current_version}" + return 1 + fi + + sed 's/^/[openclaw-migration] /' "$migration_log" + if grep -Eiq 'blocked plugin candidate|unknown channel id' "$migration_log"; then + rm -f "$migration_log" + startup_step_error "OpenClaw migration detected a blocked or missing channel plugin" + return 1 + fi + rm -f "$migration_log" + + write_shared_runtime_file "$OPENCLAW_MIGRATION_STAMP" "$current_version" + log "OpenClaw state migration completed: ${previous_version:-unversioned} -> ${current_version}" +} + seed_openclaw_plugin_registry_cache() { local seed_registry_db="${SEED_OPENCLAW_STATE_DIR}/openclaw.sqlite" local seed_registry_stamp="$SEED_OPENCLAW_PLUGIN_REGISTRY_STARTUP_STAMP" @@ -2502,6 +2537,8 @@ openclaw_supervisor_loop() { local child_pid="" local exit_code=0 local restart_delay + local started_at elapsed_seconds + local rapid_restart_count=0 local stopping=false restart_delay="$(normalize_restart_delay "$OPENCLAW_RESTART_DELAY_SECONDS" 2)" @@ -2521,6 +2558,7 @@ openclaw_supervisor_loop() { start_openclaw fi child_pid="$OPENCLAW_PID" + started_at="$SECONDS" if wait "$child_pid"; then exit_code=0 @@ -2529,11 +2567,27 @@ openclaw_supervisor_loop() { fi child_pid="" rm -f "$OPENCLAW_PID_FILE" + elapsed_seconds=$((SECONDS - started_at)) if [[ "$stopping" == "true" ]]; then break fi + if [[ "$exit_code" == "$OPENCLAW_FATAL_EXIT_CODE" ]]; then + log "OpenClaw gateway exited with fatal configuration code ${exit_code}; automatic restart disabled" + return "$exit_code" + fi + + if (( elapsed_seconds < OPENCLAW_RAPID_RESTART_WINDOW_SECONDS )); then + rapid_restart_count=$((rapid_restart_count + 1)) + else + rapid_restart_count=0 + fi + if (( rapid_restart_count >= OPENCLAW_MAX_RAPID_RESTARTS )); then + log "OpenClaw gateway rapid restart limit reached (${rapid_restart_count}); automatic restart disabled" + return "${exit_code:-1}" + fi + log "OpenClaw gateway exited with code ${exit_code}; restarting in ${restart_delay}s" sleep "$restart_delay" done @@ -2548,6 +2602,7 @@ main() { seed_runtime_content profile_step "patch_openclaw_config" patch_openclaw_config profile_step "seed.openclaw_plugin_registry_cache" seed_openclaw_plugin_registry_cache + profile_step "migrate_openclaw_state" migrate_openclaw_state profile_step "refresh_openclaw_plugin_registry" refresh_openclaw_plugin_registry profile_step "ensure_python_venv" ensure_python_venv profile_step "enforce_agent_internal_network_block" enforce_agent_internal_network_block diff --git a/docker/build-x64.sh b/docker/build-x64.sh new file mode 100755 index 0000000..1d9dfb7 --- /dev/null +++ b/docker/build-x64.sh @@ -0,0 +1,256 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +DOCKERFILE="${SCRIPT_DIR}/Dockerfile" +VENDOR_ROOT="${SCRIPT_DIR}/internal/kernel/vendor" + +IMAGE_NAME="${IMAGE_NAME:-aios-x64}" +IMAGE_TAG="${IMAGE_TAG:-local}" +NPM_REGISTRY="${AIOS_BUILD_NPM_REGISTRY:-https://registry.npmjs.org}" +CLAW_HUB_FETCH_TIMEOUT_MS="${CLAW_HUB_FETCH_TIMEOUT_MS:-180000}" +DOWNLOAD_IMAGE="${AIOS_VENDOR_DOWNLOAD_IMAGE:-curlimages/curl:8.16.0}" +NPM_IMAGE="${AIOS_VENDOR_NPM_IMAGE:-node:24.18.0-bookworm-slim}" +NO_CACHE=false +PULL=false +SKIP_VENDOR=false + +usage() { + cat <<'EOF' +用法: docker/build-x64.sh [选项] + +下载构建所需的 vendor 文件,并构建 linux/amd64 Docker 镜像。 +宿主机只需安装 Docker Engine、Bash 及基础 Linux 命令。 + +选项: + --image-name <名称> 镜像名称,默认: aios-x64 + --image-tag <标签> 镜像标签,默认: local + --version <标签> --image-tag 的别名 + --npm-registry <地址> npm registry,默认: https://registry.npmjs.org + --clawhub-fetch-timeout-ms <毫秒> ClawHub/OpenClaw 请求超时,默认: 180000 + --skip-vendor 跳过 vendor 下载,仅构建镜像 + --no-cache 禁用 Docker 构建缓存 + --pull 更新下载工具、Node 和构建基础镜像 + -h, --help 显示帮助 + +可选环境变量: + NODE_AUTH_TOKEN 私有 npm registry 的访问令牌 + AIOS_VENDOR_DOWNLOAD_IMAGE 执行文件下载的容器镜像 + AIOS_VENDOR_NPM_IMAGE 执行 npm pack 的容器镜像 +EOF +} + +fail() { + echo "错误: $*" >&2 + exit 1 +} + +require_value() { + local option="$1" + local value="${2:-}" + [[ -n "${value}" ]] || fail "${option} 缺少参数" +} + +while (( $# > 0 )); do + case "$1" in + --image-name) + require_value "$1" "${2:-}" + IMAGE_NAME="$2" + shift 2 + ;; + --image-tag|--version) + require_value "$1" "${2:-}" + IMAGE_TAG="$2" + shift 2 + ;; + --npm-registry) + require_value "$1" "${2:-}" + NPM_REGISTRY="$2" + shift 2 + ;; + --clawhub-fetch-timeout-ms) + require_value "$1" "${2:-}" + CLAW_HUB_FETCH_TIMEOUT_MS="$2" + shift 2 + ;; + --skip-vendor) + SKIP_VENDOR=true + shift + ;; + --no-cache) + NO_CACHE=true + shift + ;; + --pull) + PULL=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + fail "未知选项: $1" + ;; + esac +done + +command -v docker >/dev/null 2>&1 || fail "未找到 docker 命令" +docker info >/dev/null 2>&1 || fail "无法连接 Docker Engine,请检查服务状态和当前用户权限" +[[ -f "${DOCKERFILE}" ]] || fail "未找到 Dockerfile: ${DOCKERFILE}" + +read_docker_arg() { + local name="$1" + local value + value="$(sed -n "s/^ARG ${name}=//p" "${DOCKERFILE}" | head -n 1)" + [[ -n "${value}" ]] || fail "Dockerfile 中未定义 ARG ${name} 的默认值" + printf '%s' "${value}" +} + +download_file() { + local url="$1" + local destination="$2" + local destination_dir + local destination_name + + if [[ -s "${destination}" ]]; then + echo "[vendor] 已存在,跳过: ${destination#"${REPO_ROOT}/"}" + return + fi + + destination_dir="$(dirname "${destination}")" + destination_name="$(basename "${destination}")" + mkdir -p "${destination_dir}" + rm -f "${destination}.part" + + echo "[vendor] 下载: ${destination_name}" + docker run --rm \ + --user "$(id -u):$(id -g)" \ + --volume "${destination_dir}:/downloads" \ + "${DOWNLOAD_IMAGE}" \ + --fail --location --retry 5 --retry-all-errors \ + --output "/downloads/${destination_name}.part" \ + "${url}" + + [[ -s "${destination}.part" ]] || fail "下载结果为空: ${destination_name}" + mv "${destination}.part" "${destination}" +} + +download_npm_package() { + local package_name="$1" + local package_version="$2" + local archive_name="$3" + local npm_vendor_dir="${VENDOR_ROOT}/npm" + local destination="${npm_vendor_dir}/${archive_name}" + local docker_args=(run --rm --user "$(id -u):$(id -g)") + + if [[ -s "${destination}" ]]; then + echo "[vendor] 已存在,跳过: ${destination#"${REPO_ROOT}/"}" + return + fi + + mkdir -p "${npm_vendor_dir}" + docker_args+=(--volume "${npm_vendor_dir}:/vendor") + docker_args+=(--env "NPM_CONFIG_REGISTRY=${NPM_REGISTRY}") + docker_args+=(--env "NPM_CONFIG_CACHE=/tmp/npm-cache") + if [[ -n "${NODE_AUTH_TOKEN:-}" ]]; then + docker_args+=(--env "NODE_AUTH_TOKEN=${NODE_AUTH_TOKEN}") + fi + docker_args+=( + "${NPM_IMAGE}" + bash -ceu ' + if [[ -n "${NODE_AUTH_TOKEN:-}" ]]; then + registry_key="${NPM_CONFIG_REGISTRY#http:}" + registry_key="${registry_key#https:}" + registry_key="${registry_key%/}/" + printf "%s:_authToken=%s\n" "${registry_key}" "${NODE_AUTH_TOKEN}" > /tmp/vendor.npmrc + export NPM_CONFIG_USERCONFIG=/tmp/vendor.npmrc + fi + npm pack "$1@$2" --pack-destination /vendor + ' + -- "${package_name}" "${package_version}" + ) + + echo "[vendor] npm pack: ${package_name}@${package_version}" + docker "${docker_args[@]}" + [[ -s "${destination}" ]] || fail "npm pack 未生成预期文件: ${destination}" +} + +prepare_vendor() { + local openclaw_version + local invoke_cli_version + local clawhub_version + local qmd_version + local agent_browser_version + local mcporter_version + local chrome_version="147.0.7727.57" + + openclaw_version="$(read_docker_arg OPENCLAW_VERSION)" + invoke_cli_version="$(read_docker_arg AIOS_APPS_INVOKE_CLI_VERSION)" + clawhub_version="$(read_docker_arg CLAWHUB_VERSION)" + qmd_version="$(read_docker_arg QMD_VERSION)" + agent_browser_version="$(read_docker_arg AGENT_BROWSER_VERSION)" + mcporter_version="$(read_docker_arg MCPORTER_VERSION)" + + download_npm_package "openclaw" "${openclaw_version}" "openclaw-${openclaw_version}.tgz" + download_npm_package "clawhub" "${clawhub_version}" "clawhub-${clawhub_version}.tgz" + download_npm_package "@tobilu/qmd" "${qmd_version}" "tobilu-qmd-${qmd_version}.tgz" + download_npm_package "aios-apps-invoke-cli" "${invoke_cli_version}" "aios-apps-invoke-cli-${invoke_cli_version}.tgz" + download_npm_package "mcporter" "${mcporter_version}" "mcporter-${mcporter_version}.tgz" + download_npm_package "agent-browser" "${agent_browser_version}" "agent-browser-${agent_browser_version}.tgz" + + download_file \ + "https://storage.googleapis.com/chrome-for-testing-public/${chrome_version}/linux64/chrome-linux64.zip" \ + "${VENDOR_ROOT}/chrome/${chrome_version}/chrome-linux64.zip" + download_file \ + "https://huggingface.co/ggml-org/embeddinggemma-300M-GGUF/resolve/main/embeddinggemma-300M-Q8_0.gguf" \ + "${VENDOR_ROOT}/qmd-models/embeddinggemma-300M-Q8_0.gguf" + download_file \ + "https://huggingface.co/ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/resolve/main/qwen3-reranker-0.6b-q8_0.gguf" \ + "${VENDOR_ROOT}/qmd-models/qwen3-reranker-0.6b-q8_0.gguf" + download_file \ + "https://huggingface.co/tobil/qmd-query-expansion-1.7B-gguf/resolve/main/qmd-query-expansion-1.7B-q4_k_m.gguf" \ + "${VENDOR_ROOT}/qmd-models/qmd-query-expansion-1.7B-q4_k_m.gguf" +} + +if [[ "${PULL}" == "true" ]]; then + echo "[准备] 更新下载和 npm 工具镜像" + docker pull --platform linux/amd64 "${DOWNLOAD_IMAGE}" + docker pull --platform linux/amd64 "${NPM_IMAGE}" +fi + +if [[ "${SKIP_VENDOR}" == "false" ]]; then + prepare_vendor +fi + +IMAGE_REF="${IMAGE_NAME}:${IMAGE_TAG}" +build_args=( + build + --file "${DOCKERFILE}" + --tag "${IMAGE_REF}" + --platform linux/amd64 + --build-arg TARGETARCH=amd64 + --build-arg "AIOS_BUILD_NPM_REGISTRY=${NPM_REGISTRY}" + --build-arg "AIOS_CLAWHUB_FETCH_TIMEOUT_MS=${CLAW_HUB_FETCH_TIMEOUT_MS}" +) + +if [[ "${NO_CACHE}" == "true" ]]; then + build_args+=(--no-cache) +else + build_args+=(--no-cache-filter seed-builder) +fi + +if [[ "${PULL}" == "true" ]]; then + build_args+=(--pull) +fi + +build_args+=("${REPO_ROOT}") + +echo "[构建] ${IMAGE_REF} (linux/amd64)" +docker "${build_args[@]}" + +actual_arch="$(docker image inspect "${IMAGE_REF}" --format '{{.Architecture}}')" +[[ "${actual_arch}" == "amd64" ]] || fail "镜像架构不是 amd64,而是 ${actual_arch}" + +echo "[完成] 已生成 x64 镜像: ${IMAGE_REF}" diff --git a/docker/docs/environment-variables.md b/docker/docs/environment-variables.md index bc8c76e..9434ca4 100644 --- a/docker/docs/environment-variables.md +++ b/docker/docs/environment-variables.md @@ -38,6 +38,9 @@ AIOS Docker 支持通过 `/var/aios/env.json` 配置少量用户参数。MQTT、 | `AIOS_SERVICE_RESTART_DELAY_SECONDS` | `2` | 顶层 MQTT、MinIO、OpenClaw supervisor、apps supervisor 异常退出后的重启间隔。 | | `AIOS_APPS_RESTART_DELAY_SECONDS` | `2` | apps supervisor 内部重启 management web 和 invoke service 的间隔。 | | `OPENCLAW_RESTART_DELAY_SECONDS` | `2` | OpenClaw gateway 重启间隔。 | +| `OPENCLAW_FATAL_EXIT_CODE` | `78` | OpenClaw 配置错误退出码;命中后停止自动重启,保留现场供修复。 | +| `OPENCLAW_MAX_RAPID_RESTARTS` | `5` | 在快速失败窗口内允许的最大连续启动次数。 | +| `OPENCLAW_RAPID_RESTART_WINDOW_SECONDS` | `60` | 小于该运行时长的退出计为快速失败。 | | `AIOS_STARTUP_PROFILE` | `true` | 是否输出启动步骤耗时日志。该日志必须保留。 | ## 只读系统变量 diff --git a/docker/docs/openclaw-2026.7.1-model-comparison.md b/docker/docs/openclaw-2026.7.1-model-comparison.md new file mode 100644 index 0000000..d8970be --- /dev/null +++ b/docker/docs/openclaw-2026.7.1-model-comparison.md @@ -0,0 +1,116 @@ +# deepseek-v4-flash 与 gpt-5.4 集成测试对比 + +测试日期:2026-07-17 +OpenClaw:2026.7.1 +镜像:`aios-arm64:openclaw-2026.7.1-upgrade`(`sha256:cf6756494ae81de8c75535375bfa8d2cbfd4a7044183d2f83ee76d62a1a9026e`) + +## 结论 + +在当前 AIOS/OpenClaw 工具链和指定 OpenAI-compatible 服务上,`deepseek-v4-flash` 明显比 `gpt-5.4` 更适合作为近期生产候选,但两者均未达到完整验收标准。 + +- 两个模型的完整流程通过率均为 0/3。 +- DeepSeek 每轮都通过文件输出,且两轮运行到重启后的 Skill/记忆阶段;失败主要是最终答案收敛和严格格式遵循。 +- GPT-5.4 文件输出严格成功率为 1/3;两轮出现上游 `HTTP 400`,OpenClaw 判定为请求 schema 或 tool payload 被服务拒绝。 +- 在可比较的基础交互中,DeepSeek 的 Agent 回声平均快约 21%;模型 HTTP 200 响应样本也明显更快。 +- GPT-5.4 的问题不能全部归因于模型本身:两次 400 表明指定服务对 GPT-5.4 的工具请求兼容性也是关键变量。 + +因此,当前建议是:优先继续验证 DeepSeek;GPT-5.4 在解决上游工具请求兼容性之前,不进入生产灰度。 + +## 测试方法 + +本轮只比较两个模型,不使用历史结果充当本轮样本。 + +- 每个模型执行 3 次完整集成流程,共 6 次。 +- 每轮使用全新的容器和 Docker volume,防止记忆、会话、包和配置残留。 +- 两个模型使用相同 Base URL、同一凭据、同一镜像、同一主机、相同 context/max token 和超时配置。 +- 每轮覆盖:启动、模型注册、Agent 创建、Skill 上传、本体安装、容器重启、Agent 回声、文件输出、本体真实调用、三类包安装、重启持久化、Skill 描述、包记忆及 sender 隔离。 +- 凭据只写入临时配置;不进入报告和版本库。 +- 判定同时保留两种口径:严格断言结果,以及业务是否实际完成。这样可以区分工具失败和输出格式失败。 + +## 逐轮结果 + +| 模型 | 轮次 | 通过步骤 | 最终结果 | 失败性质 | +| --- | ---: | ---: | --- | --- | +| deepseek-v4-flash | 1 | 31 | 包记忆召回没有返回安装清单 | 把“准备检查记忆文件”作为最终 `block-idle`,长任务未收敛 | +| deepseek-v4-flash | 2 | 30 | 重启后 Skill 描述失败 | 被前序安装任务状态污染,跨任务上下文收敛失败 | +| deepseek-v4-flash | 3 | 21 | 本体版本严格断言失败 | 真实调用成功且版本正确,但附带解释文字,违反“只返回版本号” | +| gpt-5.4 | 1 | 25 | 包安装验证超时 | 包安装请求收到上游 400,返回通用错误,120 秒内未形成可验证安装状态 | +| gpt-5.4 | 2 | 20 | 文件输出会话超时 | 上游 400 拒绝 schema/tool payload;客户端最终等到 240 秒超时 | +| gpt-5.4 | 3 | 20 | 文件输出 URI 缺失 | HTTP 调用成功,但未返回 `file_output://`,工具工作流未完成 | + +完整流程严格通过率:DeepSeek 0/3,GPT-5.4 0/3。 + +## 能力检查点 + +| 检查点 | deepseek-v4-flash | gpt-5.4 | 说明 | +| --- | ---: | ---: | --- | +| 基础 Agent 回声 | 3/3 | 3/3 | 两者基础对话和 MQTT 链路均正常 | +| 文件输出严格通过 | 3/3 | 1/3 | GPT 一次 400、一次未输出 URI | +| 本体真实调用(到达样本) | 3/3 业务成功;2/3 严格成功 | 1/1 | DeepSeek 第三轮仅输出格式不符;GPT 后两轮未到达 | +| 包安装并由容器验证 | 2/2 | 0/1 | DeepSeek 第三轮未到达;GPT 首轮被 400 中断 | +| 重启后 Skill 持久化 | 2/2 | 未到达 | 静态 Skill 安装检查在所有轮次均通过 | +| 重启后 Skill 模型描述 | 1/2 | 未到达 | DeepSeek 一轮受前序任务污染 | +| 包记忆召回 | 0/1 | 未到达 | DeepSeek 停在计划文本 | + +“到达样本”只计算真正执行到该检查点的轮次,不把前序失败当作该能力本身失败。 + +## 性能 + +| 指标(3 轮平均) | deepseek-v4-flash | gpt-5.4 | +| --- | ---: | ---: | +| 首次容器启动 | 14,887ms | 14,791ms | +| 第二次容器启动 | 11,945ms | 11,186ms | +| Agent 创建 | 7,703ms | 7,366ms | +| Agent 回声 | 4,537ms | 5,743ms | + +启动和 Agent 创建主要反映平台开销,两边接近。Agent 回声更能反映实际交互,GPT-5.4 平均比 DeepSeek 慢约 1,206ms(约 27%);反过来,DeepSeek 用时约少 21%。 + +保留到日志的 HTTP 200 模型调用统计如下: + +| 模型 | HTTP 200 样本 | 平均 | P50 | P95 | 最大 | +| --- | ---: | ---: | ---: | ---: | ---: | +| deepseek-v4-flash | 45(2 轮日志) | 2,298ms | 2,250ms | 2,961ms | 4,147ms | +| gpt-5.4 | 22(3 轮日志) | 3,557ms | 3,201ms | 5,396ms | 6,483ms | + +DeepSeek 第一轮 Docker 日志未保留,因此 HTTP 延迟表只使用其余两轮,不与三轮汇总混算。GPT-5.4 另有两次 HTTP 400,未计入成功响应延迟。 + +## 失败归因 + +### deepseek-v4-flash + +主要风险是 Agent 循环的最终状态控制:模型会正确表达下一步计划,但 OpenClaw 收到的最终事件已是 `block-idle`,任务没有继续执行。另有一次严格输出格式失败。其工具覆盖较完整,文件输出、本体调用、包安装和重启持久化都获得了实际成功证据。 + +这类问题适合通过更强的任务终止约束、会话隔离、阶段状态机和自动结果校验改善,但在改进前仍会产生“看似在做、实际已结束”的假完成风险。 + +### gpt-5.4 + +GPT-5.4 同时存在两类风险: + +1. HTTP 200 时仍可能不执行文件输出工作流,不返回 URI。 +2. 3 轮中有 2 轮被指定服务以 HTTP 400 拒绝。OpenClaw 日志明确记录:`provider rejected the request schema or tool payload`。 + +第二类问题位于 OpenClaw、OpenAI-compatible 转换层和上游 GPT-5.4 接口的交界处。仅修改提示词不能可靠解决,必须抓取并最小化失败请求,确认是 tools schema、tool choice、消息历史还是流式协议兼容问题。 + +## 生产建议 + +### deepseek-v4-flash + +可作为下一阶段灰度候选,但需满足以下门槛: + +1. 将文件输出、本体调用、包安装、Skill 描述和记忆召回拆成独立会话或显式状态阶段。 +2. 对计划文本作为最终 `block-idle` 的情况自动判失败并重试,禁止标记业务完成。 +3. 对“只返回指定格式”的输出增加结果提取或结构化校验,避免业务正确但接口失败。 +4. 完整流程至少连续通过 5 次,再进入小流量灰度。 + +### gpt-5.4 + +暂不进入灰度。先完成: + +1. 捕获上游 400 对应的脱敏请求和响应,构造最小复现。 +2. 分别验证无工具、单工具、文件输出工具和多工具 schema,找出被拒字段。 +3. 工具请求连续 20 次无 400 后,再执行完整流程。 +4. 文件输出定向测试至少达到 19/20,再评估后续包安装和记忆能力。 + +## 最终选择 + +如果当前必须在两者中选择,选择 `deepseek-v4-flash`,并仅限受控灰度。理由不是它已通过验收,而是它在相同条件下表现出更完整的工具覆盖、更低的响应延迟,且没有观察到 GPT-5.4 当前存在的上游 schema/tool-payload 400 兼容故障。 diff --git a/docker/docs/openclaw-2026.7.1-regression-comparison.md b/docker/docs/openclaw-2026.7.1-regression-comparison.md new file mode 100644 index 0000000..9d1c82b --- /dev/null +++ b/docker/docs/openclaw-2026.7.1-regression-comparison.md @@ -0,0 +1,95 @@ +# OpenClaw 升级前后 DeepSeek 回归对比 + +测试日期:2026-07-17 +模型:`deepseek-v4-flash` +旧镜像:`aios-arm64:20260701.02`(`sha256:1762ca310805c923cd008b36ee6d2fa6b3f6221d45d87cb4d2bb67d1a6572e7b`,OpenClaw 2026.6.10) +新镜像:`aios-arm64:openclaw-2026.7.1-upgrade`(`sha256:cf6756494ae81de8c75535375bfa8d2cbfd4a7044183d2f83ee76d62a1a9026e`,OpenClaw 2026.7.1) + +## 结论 + +当前观察到的 DeepSeek 核心问题不是 OpenClaw 2026.7.1 升级引入的回归。 + +- 升级前后完整流程均为 0/3 严格通过。 +- “计划/进度文本被作为最终 `block-idle` 返回”在旧版两轮、新版至少一轮明确出现。 +- “业务调用正确,但没有遵守只返回指定格式”在旧版和新版各出现一轮。 +- 文件输出在旧版和新版均为 3/3 通过。 +- 包安装和重启持久化在到达该阶段的旧版、新版样本中均成功。 +- 新版出现过一轮 Skill 描述被前序任务状态污染;旧版到达该阶段的两轮均通过。这是需要继续观察的回归信号,但 1/2 对 2/2 的样本不足以证明由升级导致。 + +因此,不建议因这些模型表现问题回滚 OpenClaw。更合理的处理是继续使用 2026.7.1,并在 Agent 状态控制、会话隔离和结果解析层修复。 + +## 测试设计 + +- 两个版本均使用同一模型、Base URL、凭据、主机、提示、context/max token 和超时。 +- 每轮使用全新容器和全新 Docker volume,不复用升级后的持久数据。 +- 两个版本各取 3 次正式完整集成测试。 +- 旧镜像的管理 API 和调用日志 schema 比新版旧。测试夹具增加了两个兼容处理:创建业务系统时补充 `host`;旧调用日志没有 request/response artifact 时,以同 session、command、`success=1` 的日志作为调用证据。 +- 兼容处理不修改模型提示、Agent 会话、工具定义或最终答案断言。兼容调试阶段的运行不计入正式样本。 + +## 逐轮结果 + +| 版本 | 轮次 | 通过步骤 | 失败点 | 失败类型 | +| --- | ---: | ---: | --- | --- | +| 2026.6.10 | 1 | 31 | 包记忆召回 | 回复“先确认记录是否持久化”后直接 `block-idle` | +| 2026.6.10 | 2 | 21 | 本体版本输出 | 调用成功、版本正确,但附带解释文字 | +| 2026.6.10 | 3 | 31 | 包记忆召回 | 回复“让我检查 memory 文件”后直接 `block-idle` | +| 2026.7.1 | 1 | 31 | 包记忆召回 | 回复“检查 memory 文件”后直接 `block-idle` | +| 2026.7.1 | 2 | 30 | 重启后 Skill 描述 | 被前序安装任务的中止状态污染 | +| 2026.7.1 | 3 | 21 | 本体版本输出 | 调用成功、版本正确,但附带解释文字 | + +## 检查点对比 + +| 检查点 | 2026.6.10 | 2026.7.1 | 回归判断 | +| --- | ---: | ---: | --- | +| 完整流程严格通过 | 0/3 | 0/3 | 无证据表明新版更差;两者都未达标 | +| 基础 Agent 回声 | 3/3 | 3/3 | 无回归 | +| 文件输出严格通过 | 3/3 | 3/3 | 无回归 | +| 本体调用业务成功 | 3/3 | 3/3 | 无回归 | +| 本体输出严格通过 | 2/3 | 2/3 | 完全一致 | +| 包安装容器验证(到达样本) | 2/2 | 2/2 | 无回归 | +| 重启后 Skill 持久化(到达样本) | 2/2 | 2/2 | 无回归 | +| Skill 模型描述(到达样本) | 2/2 | 1/2 | 弱回归信号,样本不足 | +| 包记忆召回(到达样本) | 0/2 | 0/1 | 问题升级前已稳定存在 | + +## 性能对比 + +| 指标(3 轮平均) | 2026.6.10 | 2026.7.1 | 变化 | +| --- | ---: | ---: | ---: | +| 首次容器启动 | 7,102ms | 14,887ms | 新版增加 7,785ms | +| 第二次容器启动 | 10,402ms | 11,945ms | 新版增加 1,543ms | +| Agent 创建 | 6,324ms | 7,703ms | 新版增加 1,379ms | +| Agent 回声 | 4,997ms | 4,537ms | 新版减少 460ms | + +首次启动变慢是明确的升级成本,需要作为运维容量和发布窗口指标。第二次启动差距较小;基础 Agent 响应没有退化,当前样本中反而略快。 + +旧版 OpenClaw 没有输出与新版相同格式的 provider fetch 延迟日志,因此不能做严格的逐模型 HTTP P50/P95 版本对比。 + +## 问题归因 + +### 已确认不是本次升级引入 + +1. 计划文本作为最终 `block-idle`,导致任务假结束。 +2. 记忆写入/召回任务不能可靠收敛。 +3. 工具已成功但最终输出包含额外解释,违反严格格式。 + +这些现象在 2026.6.10 上已复现,且失败文本、阶段和事件类型与 2026.7.1 高度一致。 + +### 需要继续观察 + +新版一轮 Skill 描述受前序任务污染。旧版正式样本没有出现,但两个版本的到达样本都只有两轮,无法区分随机模型波动和 OpenClaw 会话处理变化。建议对此场景做独立 20 轮 A/B,而不是继续重复整套长流程。 + +### 明确的升级成本 + +1. 首次启动平均增加约 7.8 秒。 +2. 旧版管理 API、调用日志和新版测试/运维接口存在 schema 差异。 +3. 回滚仍必须配合迁移前持久卷快照,不能只替换镜像。 + +## 决策建议 + +继续采用 OpenClaw 2026.7.1,不因 DeepSeek 的上述问题回滚。上线前优先实施: + +1. 检测计划型 `block-idle`,自动续跑或明确失败。 +2. 包安装、Skill 查询和记忆召回使用独立 session,避免跨任务污染。 +3. 从工具结果生成结构化业务响应,不依赖模型严格控制最终文本。 +4. 对 Skill 跨任务污染场景做旧版/新版各 20 次定向 A/B;若新版显著更差,再作为 OpenClaw 回归单独处理。 +5. 将首次启动告警阈值和发布等待窗口按新版基线调整。 diff --git a/docker/docs/openclaw-2026.7.1-validation-report.md b/docker/docs/openclaw-2026.7.1-validation-report.md new file mode 100644 index 0000000..420a887 --- /dev/null +++ b/docker/docs/openclaw-2026.7.1-validation-report.md @@ -0,0 +1,64 @@ +# OpenClaw 2026.7.1 升级验证汇总 + +日期:2026-07-17 + +## 结论 + +OpenClaw 2026.7.1 的镜像构建、状态迁移、重复启动和基础业务链路已经验证,但当前不满足无条件生产发布标准。建议以灰度方式发布,并将 Agent 工具调用和多轮任务完成率作为阻断指标。 + +升级镜像:`aios-arm64:openclaw-2026.7.1-upgrade`(`sha256:cf6756494ae81de8c75535375bfa8d2cbfd4a7044183d2f83ee76d62a1a9026e`)。 + +## 升级改动 + +- OpenClaw 固定为 2026.7.1,Node.js 固定为 24.18.0。 +- Gateway 启动前执行按版本控制的状态迁移;版本未变化时跳过,避免每次启动重复改写。 +- 修正 MQTT seed 插件目录所有权,并增加插件注册表刷新机制。 +- 增加迁移失败后的 crash-loop 保护,避免在持久卷上无限重复破坏性操作。 +- 增加升级契约测试、构建说明和长期运维/回滚手册。 + +## 集成测试对比 + +所有模型测试使用相同的 OpenAI-compatible Base URL 和同一凭据。凭据不写入本报告或版本库。 + +| 项目 | deepseek-v4-flash(3 轮) | gpt-5.4(1 轮) | +| --- | --- | --- | +| API/传输 | 模型调用均为 HTTP 200 | 3 次模型调用均为 HTTP 200 | +| 最远进度 | 31 个步骤通过 | 20 个步骤通过 | +| 模型响应 | 第 3 轮平均 2306ms,1703-5583ms | 平均 3129ms,2692-3756ms | +| 首次启动 | 第 3 轮 15528ms | 18762ms | +| 第二次启动 | 第 3 轮 22415ms | 11050ms | +| Agent 创建 | 第 3 轮 16867ms | 8816ms | +| Agent 回声 | 第 3 轮 5592ms | 5726ms | +| 文件输出 | 通过 | 失败,未返回 `file_output://` URI | +| 包安装/重启持久化 | 通过 | 未执行到该阶段 | +| Skill/本体 | 通过 | 安装与本体静态检查通过;后续模型场景未执行到 | +| 最终失败 | 三轮均在后段 Agent 循环/记忆任务中提前结束 | 首个强工具约束的文件输出场景提前结束 | + +DeepSeek 三轮失败表现分别为:读取持久化记忆前停止;全局 Skill 回答被上一任务残留上下文污染;再次在查找持久化记忆文件前停止。共同特征是把计划或进度文本作为最终 `block-idle` 返回,没有完成最终答案。 + +GPT-5.4 已完成供应商/模型/模板/Agent 创建、权限分配、目录同步、Skill 上传、业务本体安装、启动脚本修改后的容器重启和 Agent 回声。文件输出请求触发了两次成功的模型请求,但日志中没有文件传输工具执行记录,最终返回 93 字符普通 block,导致 URI 断言失败。该证据更符合模型未遵循工具工作流,而不是 API 认证、网络或 OpenClaw Gateway 故障。 + +GPT-5.4 只有一轮样本,不能据此推断稳定失败率或完整能力上限;其失败位置较早,也使后半段性能无法与 DeepSeek直接比较。 + +## 风险评估 + +| 风险 | 等级 | 证据与影响 | 控制措施 | +| --- | --- | --- | --- | +| 模型工具调用/Agent 循环不稳定 | 高 | 两个模型均未通过完整验收;可能造成任务假完成 | 灰度流量;监控工具调用和最终答案;失败自动重试/转人工 | +| 旧版本回滚与升级后持久卷不完全兼容 | 高 | 2026.6.10 Gateway 可启动,但 MQTT seed 插件会被新版安全规则阻止 | 回滚必须恢复迁移前卷快照;不能只回滚镜像 | +| Agent 私密目录权限不兼容 | 中 | OpenClaw 报告目录权限 770 不安全,模型目录加载告警 | 将私密目录收紧为 700,并补权限回归测试 | +| 明文 Gateway token | 中 | 迁移审计提示配置包含明文 secret 字段 | 迁移到 OpenClaw SecretRef,发布前执行 secrets audit | +| 性能波动 | 中 | 启动与模型响应样本波动明显,且不同轮次不可完全横比 | 生产记录 P50/P95;以同机、同提示多轮基准设阈值 | + +## 发布门槛 + +1. 对 GPT-5.4 定向重复文件输出场景至少 5 次,确认工具 schema、提示兼容性及成功率。 +2. 对候选生产模型完整集成套件至少连续通过 3 次;不能只验证 HTTP 200 或 Gateway ready。 +3. 修正 Agent 私密目录权限并确认模型目录加载告警消失。 +4. 完成 SecretRef 迁移和 `openclaw secrets audit --check`。 +5. 创建持久卷快照并演练“旧镜像 + 迁移前快照”的完整业务回滚,验证 MQTT channel 与 Agent 回声。 +6. 灰度期监控启动耗时、Gateway ready、MQTT channel ready、模型 4xx/5xx、工具调用完成率和 `block-idle` 异常结束率。 + +## 建议 + +在当前证据下,DeepSeek 展现的功能覆盖更广,但后段多轮任务稳定性不足;GPT-5.4 在本轮更早暴露文件输出工具遵循问题。两者均不应直接作为本次升级的“完整验收通过”依据。OpenClaw 升级本身可进入受控灰度,模型工作流问题需作为独立发布门槛持续验证。 diff --git a/docker/docs/openclaw-upgrade-runbook.md b/docker/docs/openclaw-upgrade-runbook.md new file mode 100644 index 0000000..3c2a8a2 --- /dev/null +++ b/docker/docs/openclaw-upgrade-runbook.md @@ -0,0 +1,66 @@ +# OpenClaw 升级手册 + +本手册适用于保留 `/var/aios` 数据卷的 OpenClaw 内核升级。镜像升级和数据迁移必须分开管理;任何生产升级都应先使用数据卷副本验证。 + +## 升级机制 + +- 镜像固定 OpenClaw 和 Node.js 版本,避免基础镜像漂移导致同一源码产生不同运行时。 +- 每次 OpenClaw 版本变化时,kernel 在 Gateway 启动前运行一次 `openclaw doctor --non-interactive`。 +- 迁移成功后写入 `/var/aios/.openclaw/state/aios-openclaw-migrated.version`;同版本重启不会重复运行完整迁移。 +- 迁移输出出现插件被阻止或未知 channel 时启动失败,不自动删除插件和 channel 配置。 +- OpenClaw 以 `EX_CONFIG`(默认 78)退出或连续快速失败时,supervisor 停止重启,避免覆盖故障现场。 +- Dockerfile 显式列出的内置 skills 在构建时确认 ClawHub 风险提示;该确认不影响运行时由用户发起的 skill 安装。 + +## 升级前 + +1. 停止写入源数据卷的容器。 +2. 记录旧镜像 ID、OpenClaw 版本和数据卷名称。 +3. 创建数据卷快照或完整副本;只在副本上进行首次升级。 +4. 运行 `npm --prefix test run test:upgrade-contract`。 +5. 构建新镜像,并确认 `/opt/aios-toolchain/bin/openclaw --version` 和 `node --version`。 + +命名卷副本示例: + +```bash +docker volume create aios-upgrade-test +docker run --rm \ + -v aios-data:/source:ro \ + -v aios-upgrade-test:/target \ + alpine:3.22 \ + sh -c 'cd /source && tar -cf - . | tar -xf - -C /target' +``` + +## 灰度验证 + +使用副本卷启动新镜像,至少验证: + +1. 首次启动完成,迁移版本戳等于目标 OpenClaw 版本。 +2. OpenClaw 日志不存在 `blocked plugin candidate`、`unknown channel id` 和连续重启。 +3. `openclaw gateway status --json` 成功。 +4. `openclaw plugins registry --refresh` 成功,`aios-mqtt-channel` 已加载。 +5. MQTT 入站消息能路由到指定 agent,出站响应 topic 正确。 +6. 管理控制台可以列出、创建、启停和修改 agent/model。 +7. 容器重启后迁移步骤显示 `version unchanged`,Gateway 再次就绪。 + +## 生产切换 + +1. 停止旧容器,禁止旧版本继续写数据卷。 +2. 对生产卷创建时间点快照。 +3. 使用新镜像挂载生产卷,先保持单实例运行。 +4. 完成与灰度相同的健康检查后再恢复外部流量。 +5. 保留旧镜像和迁移前快照,直到观察期结束。 + +## 回滚 + +优先使用“旧镜像 + 迁移前数据卷快照”回滚。不要默认认为旧镜像可以读取新版本迁移后的状态。 + +从 `2026.7.1` 回退到 `2026.6.10` 的验证表明,旧 Gateway 可以启动并通过 RPC 检查,但 MQTT seed 插件会因旧镜像的目录所有权不满足升级后安全策略而被阻止。因此 Gateway ready 不能单独作为回滚成功标准,必须同时验证业务 channel,并恢复迁移前快照。 + +如果升级后尚未产生业务写入,可以停止新容器并恢复迁移前快照。如果已经产生新写入,应先导出需要保留的数据,再由维护人员决定合并或前向修复;不要直接让旧版本覆盖新状态。 + +## 故障处理 + +- 退出码 78:检查当日日志中的配置错误,修复配置后人工重启容器。 +- `rapid restart limit reached`:保留日志和数据卷副本,修复根因后人工重启。 +- 插件所有权被阻止:镜像内置插件必须由 root 所有且不可被非 root 用户写入;不要放宽 OpenClaw 的插件安全检查。 +- 迁移中断:不要删除迁移备份或 `.migrated` 文件;从迁移前卷副本重新验证。 diff --git a/docker/internal/kernel/vendor/npm/attention.md b/docker/internal/kernel/vendor/npm/attention.md index 75d12e0..9f2a362 100644 --- a/docker/internal/kernel/vendor/npm/attention.md +++ b/docker/internal/kernel/vendor/npm/attention.md @@ -6,7 +6,7 @@ 支持的常用命名: -- `openclaw-2026.6.10.tgz` +- `openclaw-2026.7.1.tgz` - `clawhub-0.23.0.tgz` - `tobilu-qmd-2.5.3.tgz` - `aios-apps-invoke-cli-1.0.0.tgz` @@ -22,5 +22,5 @@ npm/@tobilu/qmd/2.5.3.tgz 生成方式示例: ```bash -npm pack openclaw@2026.6.10 --pack-destination docker/internal/kernel/vendor/npm +npm pack openclaw@2026.7.1 --pack-destination docker/internal/kernel/vendor/npm ``` diff --git a/extensions/openclaw-plugins/aios-mqtt-channel/package-lock.json b/extensions/openclaw-plugins/aios-mqtt-channel/package-lock.json index f595621..58052e4 100644 --- a/extensions/openclaw-plugins/aios-mqtt-channel/package-lock.json +++ b/extensions/openclaw-plugins/aios-mqtt-channel/package-lock.json @@ -1,12 +1,12 @@ { "name": "aios-mqtt-channel", - "version": "0.1.3", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aios-mqtt-channel", - "version": "0.1.3", + "version": "1.0.5", "license": "MIT", "dependencies": { "dayjs": "^1.11.21", @@ -18,7 +18,7 @@ "@types/jasmine": "^5.1.13", "@types/node": "25.8.0", "jasmine": "^5.12.0", - "openclaw": "2026.5.28", + "openclaw": "2026.7.1", "tsx": "^4.22.0", "typescript": "6.0.3" }, @@ -1230,69 +1230,67 @@ } }, "node_modules/openclaw": { - "version": "2026.5.28", - "resolved": "https://registry.npmjs.org/openclaw/-/openclaw-2026.5.28.tgz", - "integrity": "sha512-p7jGN9wzCrqEvHNI6Y7+eh6DWoYDzJ1iQGKTm8xqQ2uQ9/2mY1CCf87WoZeb0+m3eHKSGchlI3tN33fE1lMtEA==", + "version": "2026.7.1", + "resolved": "https://registry.npmjs.org/openclaw/-/openclaw-2026.7.1.tgz", + "integrity": "sha512-ge/Xss99CHAjPL/ikmH/UFoiOrjcxDB4sW3y9mhyCD+dYW3wzV7TKbAVdkrXFgAG2d2BjpJofP97zUZ+umxo8g==", "dev": true, "hasInstallScript": true, "hasShrinkwrap": true, "license": "MIT", "dependencies": { - "@agentclientprotocol/sdk": "0.22.1", - "@anthropic-ai/sdk": "0.98.0", - "@clack/core": "1.3.1", - "@clack/prompts": "1.4.0", - "@earendil-works/pi-tui": "0.76.0", - "@google/genai": "2.6.0", + "@agentclientprotocol/sdk": "1.1.0", + "@anthropic-ai/sdk": "0.109.1", + "@clack/core": "1.4.2", + "@clack/prompts": "1.6.0", + "@earendil-works/pi-tui": "0.80.3", + "@google/genai": "2.10.0", "@grammyjs/runner": "2.0.3", "@grammyjs/transformer-throttler": "1.2.1", "@homebridge/ciao": "1.3.9", "@lydell/node-pty": "1.2.0-beta.12", - "@mistralai/mistralai": "2.2.5", + "@mistralai/mistralai": "2.4.0", "@modelcontextprotocol/sdk": "1.29.0", "@mozilla/readability": "0.6.0", - "@openclaw/fs-safe": "0.3.0", + "@openclaw/ai": "2026.7.1", + "@openclaw/fs-safe": "0.4.1", "@openclaw/proxyline": "0.3.3", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "chokidar": "5.0.0", - "clawpdf": "0.2.0", - "commander": "14.0.3", + "clawpdf": "0.3.0", + "commander": "15.0.0", "croner": "10.0.1", - "cross-spawn": "7.0.6", "diff": "9.0.0", "dotenv": "17.4.2", "express": "5.2.1", "file-type": "22.0.1", "glob": "13.0.6", - "grammy": "1.43.0", + "grammy": "1.44.0", "highlight.js": "11.11.1", - "hosted-git-info": "9.0.3", + "hosted-git-info": "10.1.1", "ignore": "7.0.5", - "ipaddr.js": "2.4.0", "jiti": "2.7.0", "json5": "2.2.3", "jszip": "3.10.1", "kysely": "0.29.2", "linkedom": "0.18.12", - "markdown-it": "14.2.0", "minimatch": "10.2.5", "node-edge-tts": "1.2.10", - "openai": "6.39.0", + "openai": "6.45.0", "partial-json": "0.1.7", - "playwright-core": "1.60.0", + "playwright-core": "1.61.1", "proper-lockfile": "4.1.2", "qrcode": "1.5.4", - "quickjs-wasi": "3.0.0", - "rastermill": "0.3.0", - "tar": "7.5.15", + "quickjs-wasi": "3.0.2", + "rastermill": "0.3.1", + "tar": "7.5.19", "tree-sitter-bash": "0.25.1", "tslog": "4.10.2", - "typebox": "1.1.38", + "typebox": "1.3.3", "typescript": "6.0.3", - "undici": "8.3.0", + "undici": "8.5.0", "web-push": "3.6.7", - "web-tree-sitter": "0.26.9", + "web-tree-sitter": "0.26.10", "ws": "8.21.0", "yaml": "2.9.0", "zod": "4.4.3" @@ -1301,16 +1299,16 @@ "openclaw": "openclaw.mjs" }, "engines": { - "node": ">=22.19.0" + "node": ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0" }, "optionalDependencies": { "sqlite-vec": "0.1.9" } }, "node_modules/openclaw/node_modules/@agentclientprotocol/sdk": { - "version": "0.22.1", - "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.22.1.tgz", - "integrity": "sha512-DfqXtl/8gO9NImq094MTaCXEU2vkhh6v7q/kT+9UjZxUqj8hYaya2OjLVIqn16MzNHcXEpShTR2RIauLSYeDQQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.1.0.tgz", + "integrity": "sha512-NT2KqphUJ3w6EksUL51ZhJgIYgq/ZLGcBPkyMKgRSO5PMVwe9DnKKX+Htnvk6KHh6dUuh34UHK4gKp+4te1Mdg==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -1318,9 +1316,9 @@ } }, "node_modules/openclaw/node_modules/@anthropic-ai/sdk": { - "version": "0.98.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.98.0.tgz", - "integrity": "sha512-N7aXtCvC5g6T1Y4V29lJjceu/zTkVkIZF0jdBvagr0TRFHuKeImffalGWEfqZKrvjH+IQbzJWw6TmSmUzrlMgg==", + "version": "0.109.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.109.1.tgz", + "integrity": "sha512-q9OnEKLr5H9nxSuXdgDgJhxfYMiE+AaUEBze2Gk91UcaaLnsN+Lx5fbCYywiqurU/APLdwv23x03Wm6WN3EBsg==", "dev": true, "license": "MIT", "dependencies": { @@ -1340,9 +1338,9 @@ } }, "node_modules/openclaw/node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "dev": true, "license": "MIT", "engines": { @@ -1361,9 +1359,9 @@ } }, "node_modules/openclaw/node_modules/@clack/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.3.1.tgz", - "integrity": "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.2.tgz", + "integrity": "sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1375,13 +1373,13 @@ } }, "node_modules/openclaw/node_modules/@clack/prompts": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.4.0.tgz", - "integrity": "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.6.0.tgz", + "integrity": "sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==", "dev": true, "license": "MIT", "dependencies": { - "@clack/core": "1.3.1", + "@clack/core": "1.4.2", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" @@ -1391,23 +1389,23 @@ } }, "node_modules/openclaw/node_modules/@earendil-works/pi-tui": { - "version": "0.76.0", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.76.0.tgz", - "integrity": "sha512-TWQEWqc38gVRYr/VTrlfePQPpJk938gNNLL1xuv0M+9cTkVr880/Q3baZQrxKoLrmN/MmVx7TyR8knYZGxTqqg==", + "version": "0.80.3", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.3.tgz", + "integrity": "sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==", "dev": true, "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", - "marked": "15.0.12" + "marked": "18.0.5" }, "engines": { "node": ">=22.19.0" } }, "node_modules/openclaw/node_modules/@google/genai": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.6.0.tgz", - "integrity": "sha512-HjoW3mPuEn7pnuKABJl9VbDoWDSF4nbwYKYvYYor7YjPeDxrrBxHzu2d1Prcd+BAuC4w+85UP6y7ZdcrQAoO7g==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.10.0.tgz", + "integrity": "sha512-e4cFxj3tiuMtsgOT4G9c1hXyGJhg7/Buj7VVeBacRY3fRtkRZZ59Q3nuVp2xbq8BGQXLXCDB253qMhklMOeUDg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1462,9 +1460,9 @@ } }, "node_modules/openclaw/node_modules/@grammyjs/types": { - "version": "3.27.3", - "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.27.3.tgz", - "integrity": "sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.28.0.tgz", + "integrity": "sha512-4JvXCdxRZHCje0M4gHzLwtB4bLno3WD28xd8CNfk4POWIu73BFnSvGeW6OQ5gPem4eYTEwkD9yDaXssixl6tMQ==", "dev": true, "license": "MIT" }, @@ -1610,15 +1608,24 @@ ] }, "node_modules/openclaw/node_modules/@mistralai/mistralai": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.5.tgz", - "integrity": "sha512-ATbWzKkNzNAZ+gtw9MI/c/ULTMG80tKUiRNIbQFfg4OP0uEZZpTfXZeBCNfs5Dq0uqMQ/tQWc4o6RRJQtMrpDA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.4.0.tgz", + "integrity": "sha512-t6hCx242MTGolB76CI+17jDtPIe/bzLsMdUTMMoMn9Qo1h02N2G5jQYHmKDGU3X//OgR2wvngTD7tO6tPp5poQ==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } } }, "node_modules/openclaw/node_modules/@modelcontextprotocol/sdk": { @@ -1672,18 +1679,36 @@ "node": ">=14.0.0" } }, + "node_modules/openclaw/node_modules/@openclaw/ai": { + "version": "2026.7.1", + "resolved": "https://registry.npmjs.org/@openclaw/ai/-/ai-2026.7.1.tgz", + "integrity": "sha512-FsKy5DXSHf4qyN8Huoz/10HZRgoEwLF4uk8UWaCafaIler+q5Fsl51HcrIqIrEe0S38OT7LOaxnR++MOshAlmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.109.1", + "@google/genai": "2.10.0", + "@mistralai/mistralai": "2.4.0", + "openai": "6.45.0", + "partial-json": "0.1.7", + "typebox": "1.3.3" + }, + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/openclaw/node_modules/@openclaw/fs-safe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.3.0.tgz", - "integrity": "sha512-uIBE441CIt1kIURoP9qRGKZ8LkGyfD9ZzeESjwAd29ZPWtghws/5GR3Pjb67jKdcJHP1I6roNXcvnhzAU7lHlA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.4.1.tgz", + "integrity": "sha512-hQi+BxO10KdRFlYUot1syC+hTaUnGeQNdqX5kwkKJig8CFq1tKsYJLPm+zkiiGsSKOprPAquQl/txejEhpKPgg==", "dev": true, "license": "MIT", "engines": { - "node": ">=20.11" + "node": ">=22" }, "optionalDependencies": { "jszip": "^3.10.1", - "tar": "7.5.13" + "tar": "7.5.19" } }, "node_modules/openclaw/node_modules/@openclaw/proxyline": { @@ -1699,6 +1724,89 @@ "undici": ">=8.3.0 <9" } }, + "node_modules/openclaw/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/openclaw/node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/openclaw/node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/openclaw/node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/openclaw/node_modules/@silvia-odwyer/photon-node": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", @@ -1738,10 +1846,20 @@ "dev": true, "license": "MIT" }, + "node_modules/openclaw/node_modules/@types/node": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, "node_modules/openclaw/node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", + "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", "dev": true, "license": "MIT" }, @@ -1843,13 +1961,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/openclaw/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/openclaw/node_modules/asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -1905,29 +2016,43 @@ } }, "node_modules/openclaw/node_modules/bn.js": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", - "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "version": "4.12.4", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.4.tgz", + "integrity": "sha512-njR1b+ixG2ufvL9Zn9JGneW+b5GV6jqpYyPPpg4QVt723b5kJPGUczkUyWEH9BwEA74UakJZ43I4FDLBF7ci0g==", "dev": true, "license": "MIT" }, "node_modules/openclaw/node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/openclaw/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1951,9 +2076,9 @@ "license": "MIT" }, "node_modules/openclaw/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -2068,11 +2193,14 @@ } }, "node_modules/openclaw/node_modules/clawpdf": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clawpdf/-/clawpdf-0.2.0.tgz", - "integrity": "sha512-Za4HD3CMRHNqOXOOyVJiQLnEuezRZR/oXiBzraTwL5XEQZuBwFxnyC1UzN4AjQWV2JrLN3ItbzfPRGE0gGOVwg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/clawpdf/-/clawpdf-0.3.0.tgz", + "integrity": "sha512-41+3AnKk9yek2sm+/9XvUlDTN8Wi+ag7fmxZuqw+ySn4lqaf/fCgLeamqPLiXY4gVbizKEHGoTG/JrIIFNE2rw==", "dev": true, "license": "MIT", + "bin": { + "clawpdf": "dist/cli.js" + }, "engines": { "node": ">=20" } @@ -2113,13 +2241,13 @@ "license": "MIT" }, "node_modules/openclaw/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", "dev": true, "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.12.0" } }, "node_modules/openclaw/node_modules/content-disposition": { @@ -2483,9 +2611,9 @@ } }, "node_modules/openclaw/node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -2546,9 +2674,9 @@ } }, "node_modules/openclaw/node_modules/eventsource-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", "dev": true, "license": "MIT", "engines": { @@ -2806,9 +2934,9 @@ } }, "node_modules/openclaw/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.5.tgz", + "integrity": "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2916,9 +3044,9 @@ } }, "node_modules/openclaw/node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", + "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2964,13 +3092,13 @@ "license": "ISC" }, "node_modules/openclaw/node_modules/grammy": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.43.0.tgz", - "integrity": "sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.44.0.tgz", + "integrity": "sha512-gGVykS5+c5f1tPV97LuU6IDRMawE2NzpwM9pNz58HQ35IZXDnYL3VOLvNzYognPSeBIOSzQXRu5w96V0aY8y8A==", "dev": true, "license": "MIT", "dependencies": { - "@grammyjs/types": "3.27.3", + "@grammyjs/types": "3.28.0", "abort-controller": "^3.0.0", "debug": "^4.4.3", "node-fetch": "^2.7.0" @@ -3014,9 +3142,9 @@ } }, "node_modules/openclaw/node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -3037,9 +3165,9 @@ } }, "node_modules/openclaw/node_modules/hono": { - "version": "4.12.18", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", - "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "dev": true, "license": "MIT", "engines": { @@ -3047,16 +3175,16 @@ } }, "node_modules/openclaw/node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-10.1.1.tgz", + "integrity": "sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==", "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^11.1.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/openclaw/node_modules/html-escaper": { @@ -3217,13 +3345,13 @@ } }, "node_modules/openclaw/node_modules/ipaddr.js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", - "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 0.10" } }, "node_modules/openclaw/node_modules/is-fullwidth-code-point": { @@ -3409,26 +3537,6 @@ } } }, - "node_modules/openclaw/node_modules/linkify-it": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", - "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, "node_modules/openclaw/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3450,54 +3558,26 @@ "license": "Apache-2.0" }, "node_modules/openclaw/node_modules/lru-cache": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", - "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, - "node_modules/openclaw/node_modules/markdown-it": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", - "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.1", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, "node_modules/openclaw/node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", "dev": true, "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/openclaw/node_modules/math-intrinsics": { @@ -3510,13 +3590,6 @@ "node": ">= 0.4" } }, - "node_modules/openclaw/node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true, - "license": "MIT" - }, "node_modules/openclaw/node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -3641,9 +3714,9 @@ } }, "node_modules/openclaw/node_modules/node-addon-api": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", - "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz", + "integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==", "dev": true, "license": "MIT", "engines": { @@ -3767,19 +3840,28 @@ } }, "node_modules/openclaw/node_modules/openai": { - "version": "6.39.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.39.0.tgz", - "integrity": "sha512-O61LIsimY3acVabwvomwFhwrnN36yvHY2quIfy9keEcFytGgWeV35yLHQ6NVMLSBxRpHmcg2yuhCnlu2HT4pLQ==", + "version": "6.45.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.45.0.tgz", + "integrity": "sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==", "dev": true, "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, "ws": { "optional": true }, @@ -3924,9 +4006,9 @@ } }, "node_modules/openclaw/node_modules/playwright-core": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", - "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3976,13 +4058,24 @@ } }, "node_modules/openclaw/node_modules/protobufjs": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.4.0.tgz", - "integrity": "sha512-iriNhQ57SYA5Jbdi+41AyPdx6jPPkFO7DODzkOBmqFhgYn/JzX2HxgxYPY18eQAs3CP/AWqtPvkWn8rclRAxdQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz", + "integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==", "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", "long": "^5.3.2" }, "engines": { @@ -4003,26 +4096,6 @@ "node": ">= 0.10" } }, - "node_modules/openclaw/node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/openclaw/node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/openclaw/node_modules/qrcode": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", @@ -4129,33 +4202,37 @@ } }, "node_modules/openclaw/node_modules/quickjs-wasi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.0.0.tgz", - "integrity": "sha512-X7ouKC4ZVf9bXQ8rsE7+L6TeBbesejAJH61x16xRaGAQGfBHHRcniWgzJZZVtHc8rS9yVsY+Tvk8/usAosg4bg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.0.2.tgz", + "integrity": "sha512-SyfPzlrfz67/kv0SogmQgW4c2I1klkLcbvj9Y2gc1h7+VylmvuGevFljLXibGKajKJKiJV29d4S6FQLA6Sc80A==", "dev": true, "license": "MIT" }, "node_modules/openclaw/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/openclaw/node_modules/rastermill": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/rastermill/-/rastermill-0.3.0.tgz", - "integrity": "sha512-4g2i0I7M5sba//lFBh19Wi0hDGw8o+isnt/BtEyqQXIZaYclhcNBwL/Fw/6gDCp7aaLwQHADuUvyHCB0Oat5Vw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/rastermill/-/rastermill-0.3.1.tgz", + "integrity": "sha512-CX4nij6+ZLHYIaojJNfLTr7W+AiH/IPJi6E9Aw1br2///1KZL2KBOHd68rkcLedc47MPvb4hhH+fzYeGFa4A/Q==", "dev": true, "license": "MIT", "dependencies": { "@silvia-odwyer/photon-node": "0.3.4" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "node_modules/openclaw/node_modules/raw-body": { @@ -4385,15 +4462,15 @@ } }, "node_modules/openclaw/node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -4664,9 +4741,9 @@ } }, "node_modules/openclaw/node_modules/tar": { - "version": "7.5.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", - "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "version": "7.5.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", + "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -4797,9 +4874,9 @@ } }, "node_modules/openclaw/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.3.tgz", + "integrity": "sha512-URXGUE31PJDQC+PtRMJeLdF4kmmOdFoVPikPCtV2oOIhUpNpppEdIz7W8bH8cFYPYHdDpaRvqwdegMTmHliudg==", "dev": true, "license": "MIT" }, @@ -4817,13 +4894,6 @@ "node": ">=14.17" } }, - "node_modules/openclaw/node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, - "license": "MIT" - }, "node_modules/openclaw/node_modules/uhyphen": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", @@ -4845,15 +4915,22 @@ } }, "node_modules/openclaw/node_modules/undici": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.3.0.tgz", - "integrity": "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", + "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", "dev": true, "license": "MIT", "engines": { "node": ">=22.19.0" } }, + "node_modules/openclaw/node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/openclaw/node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4912,9 +4989,9 @@ } }, "node_modules/openclaw/node_modules/web-tree-sitter": { - "version": "0.26.9", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.9.tgz", - "integrity": "sha512-YJwSHANl6XFgeEjB8nitgj0qZYt5gkIesJ4w2srS2wcLB4GUa4xcOkM0YaMsU6WNR53YVIkDSY7Ej4pf3IXtCA==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.10.tgz", + "integrity": "sha512-vengBGYS7FpAerkR3o04oBL4L8MkVmjawK50AFBu7v0HZBkmF9ZavPGKoXLSSmRhp7T/YgsJ7joAS3yAxHPEqQ==", "dev": true, "license": "MIT" }, @@ -5043,9 +5120,9 @@ } }, "node_modules/openclaw/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { diff --git a/extensions/openclaw-plugins/aios-mqtt-channel/package.json b/extensions/openclaw-plugins/aios-mqtt-channel/package.json index 116637a..4fa3305 100644 --- a/extensions/openclaw-plugins/aios-mqtt-channel/package.json +++ b/extensions/openclaw-plugins/aios-mqtt-channel/package.json @@ -62,7 +62,7 @@ "@types/jasmine": "^5.1.13", "@types/node": "25.8.0", "jasmine": "^5.12.0", - "openclaw": "2026.5.28", + "openclaw": "2026.7.1", "tsx": "^4.22.0", "typescript": "6.0.3" }, diff --git a/test/case/integration/playwright-flow.js b/test/case/integration/playwright-flow.js index 896e64f..a5c6769 100644 --- a/test/case/integration/playwright-flow.js +++ b/test/case/integration/playwright-flow.js @@ -1090,6 +1090,7 @@ async function createBusinessSystem(baseUrl, token) { application_name: BUSINESS_SYSTEM_APP, description: "Container integration OTT ontology test", scheme: connection.scheme, + host: connection.host, port: connection.port, status: "active" }, { @@ -2061,12 +2062,18 @@ async function verifyBusinessSystemInvocationLog(baseUrl, token, sessionId, time || valueContainsText(responseArtifact.payload, ONTOLOGY_EXPECTED_VERSION) || valueContainsText(responseArtifact.text, ONTOLOGY_EXPECTED_VERSION) ); + const legacyLogWithoutArtifacts = ( + !candidate?.request_artifact + && !candidate?.response_artifact + && (candidate?.success === true || Number(candidate?.success) === 1) + ); - if (requestMatches && responseMatches) { + if ((requestMatches && responseMatches) || legacyLogWithoutArtifacts) { return { ok: true, sessionId, responseTimeMs: Date.now() - startedAt, + legacyEvidence: legacyLogWithoutArtifacts, log: { id: candidate.id ?? null, traceId: candidate.trace_id ?? null, @@ -3403,87 +3410,91 @@ async function main() { sessionId: report.agentPageEcho.sessionId }); - try { - report.fileOutput = await externalAgentFileOutput( - baseUrl, - serviceToken, - envJsonPath, - restartTargets, - readTimeout("agentMqtt", 240000) - ); - addStep(report, "7a.external-agent-file-output", true, { - fileName: report.fileOutput.fileName, - uri: report.fileOutput.uri, - object: report.fileOutput.object, - responseTimeMs: report.fileOutput.responseTimeMs, - sessionId: report.fileOutput.sessionId, - topics: report.fileOutput.topics, - reply: report.fileOutput.reply - }); - } catch (error) { - const details = error?.details?.fileOutput; - if (details) { - report.fileOutput = details; - addStep(report, "7a.external-agent-file-output", false, { - fileName: details.fileName || null, - uri: details.uri || null, - object: details.object || null, - responseTimeMs: details.responseTimeMs ?? null, - sessionId: details.sessionId ?? null, - topics: details.topics ?? null, - reply: details.reply ?? null, - observedReplyMessages: details.observedReplyMessages ?? [], - error: error instanceof Error ? error.message : String(error) + if (focus !== "skill-context-contamination") { + try { + report.fileOutput = await externalAgentFileOutput( + baseUrl, + serviceToken, + envJsonPath, + restartTargets, + readTimeout("agentMqtt", 240000) + ); + addStep(report, "7a.external-agent-file-output", true, { + fileName: report.fileOutput.fileName, + uri: report.fileOutput.uri, + object: report.fileOutput.object, + responseTimeMs: report.fileOutput.responseTimeMs, + sessionId: report.fileOutput.sessionId, + topics: report.fileOutput.topics, + reply: report.fileOutput.reply }); + } catch (error) { + const details = error?.details?.fileOutput; + if (details) { + report.fileOutput = details; + addStep(report, "7a.external-agent-file-output", false, { + fileName: details.fileName || null, + uri: details.uri || null, + object: details.object || null, + responseTimeMs: details.responseTimeMs ?? null, + sessionId: details.sessionId ?? null, + topics: details.topics ?? null, + reply: details.reply ?? null, + observedReplyMessages: details.observedReplyMessages ?? [], + error: error instanceof Error ? error.message : String(error) + }); + } + throw error; } - throw error; - } - try { - report.ontologyVersion = await externalAgentOntologyVersion( - baseUrl, - serviceToken, - envJsonPath, - readTimeout("agentMqtt", 240000) - ); - addStep(report, "7b.external-agent-ontology-version", true, { - expectedVersion: ONTOLOGY_EXPECTED_VERSION, - actualVersion: report.ontologyVersion.actualVersion || null, - responseTimeMs: report.ontologyVersion.responseTimeMs, - sessionId: report.ontologyVersion.sessionId, - topics: report.ontologyVersion.topics, - reply: report.ontologyVersion.reply - }); - } catch (error) { - const details = error?.details?.ontologyVersion; - if (details) { - report.ontologyVersion = details; - addStep(report, "7b.external-agent-ontology-version", false, { + try { + report.ontologyVersion = await externalAgentOntologyVersion( + baseUrl, + serviceToken, + envJsonPath, + readTimeout("agentMqtt", 240000) + ); + addStep(report, "7b.external-agent-ontology-version", true, { expectedVersion: ONTOLOGY_EXPECTED_VERSION, - actualVersion: details.actualVersion || null, - responseTimeMs: details.responseTimeMs ?? null, - sessionId: details.sessionId ?? null, - topics: details.topics ?? null, - reply: details.reply ?? null, - observedReplyMessages: details.observedReplyMessages ?? [], - businessSystemProbe: details.businessSystemProbe ?? null, - error: error instanceof Error ? error.message : String(error) + actualVersion: report.ontologyVersion.actualVersion || null, + responseTimeMs: report.ontologyVersion.responseTimeMs, + sessionId: report.ontologyVersion.sessionId, + topics: report.ontologyVersion.topics, + reply: report.ontologyVersion.reply }); + } catch (error) { + const details = error?.details?.ontologyVersion; + if (details) { + report.ontologyVersion = details; + addStep(report, "7b.external-agent-ontology-version", false, { + expectedVersion: ONTOLOGY_EXPECTED_VERSION, + actualVersion: details.actualVersion || null, + responseTimeMs: details.responseTimeMs ?? null, + sessionId: details.sessionId ?? null, + topics: details.topics ?? null, + reply: details.reply ?? null, + observedReplyMessages: details.observedReplyMessages ?? [], + businessSystemProbe: details.businessSystemProbe ?? null, + error: error instanceof Error ? error.message : String(error) + }); + } + throw error; } - throw error; - } - report.ontologyInvocationLog = await verifyBusinessSystemInvocationLog( - baseUrl, - token, - report.ontologyVersion.sessionId, - 60000 - ); - addStep(report, "7b.verify-business-system-invocation-log", true, { - sessionId: report.ontologyInvocationLog.sessionId, - responseTimeMs: report.ontologyInvocationLog.responseTimeMs, - log: report.ontologyInvocationLog.log - }); + report.ontologyInvocationLog = await verifyBusinessSystemInvocationLog( + baseUrl, + token, + report.ontologyVersion.sessionId, + 60000 + ); + addStep(report, "7b.verify-business-system-invocation-log", true, { + sessionId: report.ontologyInvocationLog.sessionId, + responseTimeMs: report.ontologyInvocationLog.responseTimeMs, + log: report.ontologyInvocationLog.log + }); + } else { + addStep(report, "7a.skip-file-output-and-ontology-for-skill-context-focus", true, { focus }); + } const waitBeforePackageInstallStartedAt = new Date().toISOString(); const waitBeforePackageInstallTargets = parseRestartTargets(); @@ -3569,6 +3580,11 @@ async function main() { reply: report.agentGlobalSkillDescription.reply }); + if (focus === "skill-context-contamination") { + report.ok = true; + return; + } + report.packageMemory = await agentPageRecallInstalledPackages( page, baseUrl, diff --git a/test/case/upgrade/openclaw-upgrade-contract.test.js b/test/case/upgrade/openclaw-upgrade-contract.test.js new file mode 100644 index 0000000..aa67a98 --- /dev/null +++ b/test/case/upgrade/openclaw-upgrade-contract.test.js @@ -0,0 +1,57 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const testDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(testDir, "../../.."); + +async function readRepoFile(relativePath) { + return await readFile(path.join(repoRoot, relativePath), "utf8"); +} + +test("Docker image pins the supported OpenClaw and Node versions", async () => { + const dockerfile = await readRepoFile("docker/Dockerfile"); + + assert.match(dockerfile, /^ARG OPENCLAW_VERSION=2026\.7\.1$/m); + assert.match(dockerfile, /^ARG NODE_VERSION=24\.18\.0$/m); + assert.match(dockerfile, /^FROM node:\$\{NODE_VERSION\}-bookworm-slim AS runtime-base$/m); + assert.match(dockerfile, /^FROM node:\$\{NODE_VERSION\}-bookworm-slim AS web-installer$/m); +}); + +test("seed MQTT plugin is trusted by the OpenClaw runtime user", async () => { + const dockerfile = await readRepoFile("docker/Dockerfile"); + + assert.match( + dockerfile, + /chown -R root:"\$\{AIOS_OPENCLAW_GROUP\}" "\$\{seed_mqtt_project\}"/ + ); + assert.match( + dockerfile, + /openclaw skills install "\$\{skill\}" --global --force --acknowledge-clawhub-risk/ + ); +}); + +test("kernel startup performs versioned migration before plugin refresh", async () => { + const startup = await readRepoFile("docker/assets/scripts/kernel-startup.sh"); + const patchIndex = startup.indexOf('profile_step "patch_openclaw_config"'); + const migrationIndex = startup.indexOf('profile_step "migrate_openclaw_state"'); + const refreshIndex = startup.indexOf('profile_step "refresh_openclaw_plugin_registry"'); + + assert.ok(patchIndex >= 0, "config patch step is missing"); + assert.ok(migrationIndex > patchIndex, "migration must run after the AIOS config patch"); + assert.ok(refreshIndex > migrationIndex, "plugin refresh must run after migration"); + assert.match(startup, /doctor --non-interactive/); + assert.match(startup, /blocked plugin candidate\|unknown channel id/); + assert.match(startup, /\.plugins\.bundledDiscovery = \(.plugins.bundledDiscovery \/\/ "compat"\)/); +}); + +test("gateway supervisor stops fatal and repeated rapid restart loops", async () => { + const startup = await readRepoFile("docker/assets/scripts/kernel-startup.sh"); + + assert.match(startup, /OPENCLAW_FATAL_EXIT_CODE="\$\{OPENCLAW_FATAL_EXIT_CODE:-78\}"/); + assert.match(startup, /OPENCLAW_MAX_RAPID_RESTARTS="\$\{OPENCLAW_MAX_RAPID_RESTARTS:-5\}"/); + assert.match(startup, /if \[\[ "\$exit_code" == "\$OPENCLAW_FATAL_EXIT_CODE" \]\]/); + assert.match(startup, /rapid restart limit reached/); +}); diff --git a/test/package.json b/test/package.json index 6bf2216..09c1c21 100644 --- a/test/package.json +++ b/test/package.json @@ -3,6 +3,7 @@ "private": true, "type": "module", "scripts": { + "test:upgrade-contract": "node --test ./case/upgrade/openclaw-upgrade-contract.test.js", "case:integration": "node ./case/integration/playwright-flow.js", "case:stress": "node ./case/stress/stress-flow.js", "test:flow": "node ./suite/integration/run-integration-suite.js full", @@ -20,4 +21,3 @@ "playwright-core": "^1.60.0" } } - -- Gitee From 71eaf6ffbbaec305c8abe2561c60a5a9c0632ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=81=E4=BC=9F?= <55093136@qq.com> Date: Fri, 17 Jul 2026 22:34:39 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E4=BC=98=E5=85=88=E4=BB=8E=E4=BB=93?= =?UTF-8?q?=E5=BA=93=E4=B8=AD=E7=BC=96=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/BUILD.md | 9 +-- docker/Dockerfile | 66 ++++++++++++++----- docker/Dockerfile.dockerignore | 9 +++ docker/build-x64.sh | 3 - .../upgrade/openclaw-upgrade-contract.test.js | 23 +++++++ 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/docker/BUILD.md b/docker/BUILD.md index 7ba18b5..709480b 100644 --- a/docker/BUILD.md +++ b/docker/BUILD.md @@ -2,11 +2,12 @@ `docker/` 是唯一保留的 Docker 构建入口。仓库不再提供独立的 kernel/apps Dockerfile,也不再需要先构建子镜像。 -构建过程中只复制 Docker 启动脚本、运行模板和离线二进制资源。核心运行逻辑和站点包通过版本化安装获取: +构建过程中会直接使用当前仓库内的 AIOS 源码,并通过版本化安装获取第三方工具: -- `openclaw`、`clawhub`、`@tobilu/qmd`、`mcporter`、`agent-browser`、`aios-apps-invoke-cli` 通过 npm 安装。 -- `aios-management-web` 通过 npm 安装,并把包内 `dist`、`server`、`scripts` 放入镜像。 -- OpenClaw 插件和全局 skills 通过 `openclaw plugins install` / `openclaw skills install` 从 npm/clawhub 安装。 +- `aios-apps-invoke-cli` 和 `aios-mqtt-channel` 从当前仓库源码构建、打包并安装。 +- `aios-management-web` 从当前仓库源码构建,并把 `dist`、`server`、`scripts` 放入镜像。 +- `aios-call-app-service`、`aios-transfer-file`、`aios-make-chart-image`、`aios-self-improving-agent` 从当前仓库复制;其余全局 skills 仍通过 ClawHub 安装。 +- `openclaw`、`clawhub`、`@tobilu/qmd`、`mcporter`、`agent-browser` 等第三方工具通过 npm 安装。 Docker 资产统一放在 `docker/assets`;`docker/internal` 只保留构建脚本和离线 vendor 资源。 diff --git a/docker/Dockerfile b/docker/Dockerfile index 1d34959..dcde3d8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,10 +3,38 @@ ARG OPENCLAW_VERSION=2026.7.1 ARG NODE_VERSION=24.18.0 ARG AEDES_VERSION=1.1.0 -ARG AIOS_APPS_INVOKE_CLI_VERSION=1.0.3 -ARG AIOS_MANAGEMENT_WEB_NPM_VERSION=1.0.17 ARG AIOS_BUILD_NPM_REGISTRY=https://registry.npmjs.org +FROM node:${NODE_VERSION}-bookworm-slim AS local-package-builder + +ARG AIOS_BUILD_NPM_REGISTRY=https://registry.npmjs.org + +WORKDIR /src/aios-apps-invoke-cli +COPY packages/aios-apps-invoke-cli/package.json packages/aios-apps-invoke-cli/package-lock.json ./ +RUN --mount=type=cache,target=/root/.npm,sharing=locked \ + NPM_CONFIG_REGISTRY="${AIOS_BUILD_NPM_REGISTRY}" npm ci --ignore-scripts +COPY packages/aios-apps-invoke-cli/README.md packages/aios-apps-invoke-cli/tsconfig.json packages/aios-apps-invoke-cli/tsconfig.build.json ./ +COPY packages/aios-apps-invoke-cli/src ./src +RUN set -eux; \ + npm run build; \ + mkdir -p /build/npm; \ + npm pack --ignore-scripts --pack-destination /build/npm; \ + mv /build/npm/aios-apps-invoke-cli-*.tgz /build/npm/aios-apps-invoke-cli.tgz; \ + node -p 'require("./package.json").version' > /build/npm/aios-apps-invoke-cli.version + +WORKDIR /src/aios-mqtt-channel +COPY extensions/openclaw-plugins/aios-mqtt-channel/package.json extensions/openclaw-plugins/aios-mqtt-channel/package-lock.json ./ +RUN --mount=type=cache,target=/root/.npm,sharing=locked \ + NPM_CONFIG_REGISTRY="${AIOS_BUILD_NPM_REGISTRY}" npm ci +COPY extensions/openclaw-plugins/aios-mqtt-channel/tsconfig.json ./ +COPY extensions/openclaw-plugins/aios-mqtt-channel/*.ts ./ +COPY extensions/openclaw-plugins/aios-mqtt-channel/src ./src +COPY extensions/openclaw-plugins/aios-mqtt-channel/openclaw.plugin.json extensions/openclaw-plugins/aios-mqtt-channel/README.md extensions/openclaw-plugins/aios-mqtt-channel/CHANGELOG.md extensions/openclaw-plugins/aios-mqtt-channel/.env.example extensions/openclaw-plugins/aios-mqtt-channel/config.example.json ./ +RUN set -eux; \ + npm run build; \ + npm pack --ignore-scripts --pack-destination /build/npm; \ + mv /build/npm/aios-mqtt-channel-*.tgz /build/npm/aios-mqtt-channel.tgz + FROM node:${NODE_VERSION}-bookworm-slim AS runtime-base ARG TARGETARCH @@ -185,7 +213,6 @@ ARG CLAWHUB_VERSION=0.23.0 ARG QMD_VERSION=2.5.3 ARG AGENT_BROWSER_VERSION=0.31.0 ARG MCPORTER_VERSION=0.12.0 -ARG AIOS_APPS_INVOKE_CLI_VERSION ARG AIOS_BUILD_NPM_REGISTRY=https://registry.npmjs.org ARG AIOS_CLAWHUB_FETCH_TIMEOUT_MS=300000 @@ -235,10 +262,11 @@ RUN --mount=type=bind,source=docker/internal/kernel/vendor,target=/tmp/aios-vend --mount=type=cache,target=/opt/aios-build-cache/npm-tools,sharing=locked \ install-versioned-npm-global "@tobilu/qmd" "${QMD_VERSION}" -RUN --mount=type=bind,source=docker/internal/kernel/vendor,target=/tmp/aios-vendor-src,readonly \ +RUN --mount=type=bind,from=local-package-builder,source=/build/npm,target=/tmp/aios-local-npm,readonly \ --mount=type=cache,target=/root/.npm,sharing=locked \ --mount=type=cache,target=/opt/aios-build-cache/npm-tools,sharing=locked \ - install-versioned-npm-global "aios-apps-invoke-cli" "${AIOS_APPS_INVOKE_CLI_VERSION}" + AIOS_BUILD_NPM_VENDOR_DIR=/tmp/aios-local-npm \ + install-versioned-npm-global "aios-apps-invoke-cli" "$(cat /tmp/aios-local-npm/aios-apps-invoke-cli.version)" RUN --mount=type=bind,source=docker/internal/kernel/vendor,target=/tmp/aios-vendor-src,readonly \ --mount=type=cache,target=/root/.npm,sharing=locked \ @@ -321,21 +349,25 @@ RUN set -eux; \ > /usr/local/bin/install-seed-skill; \ chmod +x /usr/local/bin/install-seed-skill -RUN openclaw plugins install aios-mqtt-channel +RUN --mount=type=bind,from=local-package-builder,source=/build/npm,target=/tmp/aios-local-npm,readonly \ + --mount=type=cache,target=/root/.npm,sharing=locked \ + set -eux; \ + mqtt_project="${AIOS_OPENCLAW_HOME}/npm/projects/aios-mqtt-channel"; \ + mkdir -p "${mqtt_project}"; \ + NPM_CONFIG_REGISTRY="${AIOS_BUILD_NPM_REGISTRY}" npm install --omit=dev --prefix "${mqtt_project}" /tmp/aios-local-npm/aios-mqtt-channel.tgz; \ + test -f "${mqtt_project}/node_modules/aios-mqtt-channel/openclaw.plugin.json" -RUN skill="aios-call-app-service"; \ - install-seed-skill "${skill}" +COPY extensions/clawhub-skills/aios-call-app-service ${AIOS_OPENCLAW_HOME}/skills/aios-call-app-service -RUN skill="aios-transfer-file"; \ - install-seed-skill "${skill}"; \ - NPM_CONFIG_REGISTRY="${AIOS_BUILD_NPM_REGISTRY}" npm install --omit=dev --prefix "${AIOS_OPENCLAW_HOME}/skills/aios-transfer-file" +COPY extensions/clawhub-skills/aios-transfer-file ${AIOS_OPENCLAW_HOME}/skills/aios-transfer-file +RUN --mount=type=cache,target=/root/.npm,sharing=locked \ + NPM_CONFIG_REGISTRY="${AIOS_BUILD_NPM_REGISTRY}" npm ci --omit=dev --prefix "${AIOS_OPENCLAW_HOME}/skills/aios-transfer-file" -RUN skill="aios-make-chart-image"; \ - install-seed-skill "${skill}"; \ - NPM_CONFIG_REGISTRY="${AIOS_BUILD_NPM_REGISTRY}" npm install --omit=dev --prefix "${AIOS_OPENCLAW_HOME}/skills/aios-make-chart-image" +COPY extensions/clawhub-skills/aios-make-chart-image ${AIOS_OPENCLAW_HOME}/skills/aios-make-chart-image +RUN --mount=type=cache,target=/root/.npm,sharing=locked \ + NPM_CONFIG_REGISTRY="${AIOS_BUILD_NPM_REGISTRY}" npm ci --omit=dev --prefix "${AIOS_OPENCLAW_HOME}/skills/aios-make-chart-image" -RUN skill="aios-self-improving-agent"; \ - install-seed-skill "${skill}" +COPY extensions/clawhub-skills/aios-self-improving-agent ${AIOS_OPENCLAW_HOME}/skills/aios-self-improving-agent RUN skill="@steipete/video-frames"; \ install-seed-skill "${skill}" @@ -381,7 +413,6 @@ RUN set -eux; \ FROM node:${NODE_VERSION}-bookworm-slim AS web-installer -ARG AIOS_MANAGEMENT_WEB_NPM_VERSION ARG AIOS_BUILD_NPM_REGISTRY=https://registry.npmjs.org WORKDIR /tmp/web-src @@ -414,7 +445,6 @@ FROM runtime-base AS final ARG TARGETARCH ARG OPENCLAW_VERSION ARG AEDES_VERSION -ARG AIOS_MANAGEMENT_WEB_NPM_VERSION ARG AIOS_BUILD_NPM_REGISTRY ENV DEBIAN_FRONTEND=noninteractive diff --git a/docker/Dockerfile.dockerignore b/docker/Dockerfile.dockerignore index 77eb523..44c04e7 100644 --- a/docker/Dockerfile.dockerignore +++ b/docker/Dockerfile.dockerignore @@ -18,8 +18,16 @@ !docker/sample/env.json !docker/sample/run_after_startup.sh !packages/ +!packages/aios-apps-invoke-cli/ +!packages/aios-apps-invoke-cli/** !packages/aios-management-web/ !packages/aios-management-web/** +!extensions/ +!extensions/clawhub-skills/ +!extensions/clawhub-skills/** +!extensions/openclaw-plugins/ +!extensions/openclaw-plugins/aios-mqtt-channel/ +!extensions/openclaw-plugins/aios-mqtt-channel/** test/ test/** @@ -34,6 +42,7 @@ docker/integration/** **/*.temp **/.env **/.env.* +!extensions/openclaw-plugins/aios-mqtt-channel/.env.example **/node_modules **/node_modules/** **/dist diff --git a/docker/build-x64.sh b/docker/build-x64.sh index 1d9dfb7..6022e7e 100755 --- a/docker/build-x64.sh +++ b/docker/build-x64.sh @@ -179,7 +179,6 @@ download_npm_package() { prepare_vendor() { local openclaw_version - local invoke_cli_version local clawhub_version local qmd_version local agent_browser_version @@ -187,7 +186,6 @@ prepare_vendor() { local chrome_version="147.0.7727.57" openclaw_version="$(read_docker_arg OPENCLAW_VERSION)" - invoke_cli_version="$(read_docker_arg AIOS_APPS_INVOKE_CLI_VERSION)" clawhub_version="$(read_docker_arg CLAWHUB_VERSION)" qmd_version="$(read_docker_arg QMD_VERSION)" agent_browser_version="$(read_docker_arg AGENT_BROWSER_VERSION)" @@ -196,7 +194,6 @@ prepare_vendor() { download_npm_package "openclaw" "${openclaw_version}" "openclaw-${openclaw_version}.tgz" download_npm_package "clawhub" "${clawhub_version}" "clawhub-${clawhub_version}.tgz" download_npm_package "@tobilu/qmd" "${qmd_version}" "tobilu-qmd-${qmd_version}.tgz" - download_npm_package "aios-apps-invoke-cli" "${invoke_cli_version}" "aios-apps-invoke-cli-${invoke_cli_version}.tgz" download_npm_package "mcporter" "${mcporter_version}" "mcporter-${mcporter_version}.tgz" download_npm_package "agent-browser" "${agent_browser_version}" "agent-browser-${agent_browser_version}.tgz" diff --git a/test/case/upgrade/openclaw-upgrade-contract.test.js b/test/case/upgrade/openclaw-upgrade-contract.test.js index aa67a98..0d21a45 100644 --- a/test/case/upgrade/openclaw-upgrade-contract.test.js +++ b/test/case/upgrade/openclaw-upgrade-contract.test.js @@ -33,6 +33,29 @@ test("seed MQTT plugin is trusted by the OpenClaw runtime user", async () => { ); }); +test("first-party npm packages and skills are installed from the repository", async () => { + const dockerfile = await readRepoFile("docker/Dockerfile"); + const buildScript = await readRepoFile("docker/build-x64.sh"); + + assert.match(dockerfile, /^FROM node:\$\{NODE_VERSION\}-bookworm-slim AS local-package-builder$/m); + assert.match(dockerfile, /COPY packages\/aios-apps-invoke-cli\/src \.\/src/); + assert.match(dockerfile, /AIOS_BUILD_NPM_VENDOR_DIR=\/tmp\/aios-local-npm/); + assert.match(dockerfile, /npm install --omit=dev --prefix "\$\{mqtt_project\}" \/tmp\/aios-local-npm\/aios-mqtt-channel\.tgz/); + for (const skill of [ + "aios-call-app-service", + "aios-transfer-file", + "aios-make-chart-image", + "aios-self-improving-agent" + ]) { + assert.match( + dockerfile, + new RegExp(`COPY extensions/clawhub-skills/${skill} \\$\\{AIOS_OPENCLAW_HOME\\}/skills/${skill}`) + ); + assert.doesNotMatch(dockerfile, new RegExp(`skill="${skill}";`)); + } + assert.doesNotMatch(buildScript, /download_npm_package "aios-apps-invoke-cli"/); +}); + test("kernel startup performs versioned migration before plugin refresh", async () => { const startup = await readRepoFile("docker/assets/scripts/kernel-startup.sh"); const patchIndex = startup.indexOf('profile_step "patch_openclaw_config"'); -- Gitee From c49fda8e9c8d738538c448b4415b7e97a42b46e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=81=E4=BC=9F?= <55093136@qq.com> Date: Sun, 19 Jul 2026 15:26:02 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E6=9B=B4=E6=8D=A2vi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docker/docs/openclaw-skill-context-ab-40.md | 108 ++++++++++++++++++ packages/aios-management-web/README.md | 4 +- packages/aios-management-web/index.html | 2 +- .../public/ikkyu-abstract.webp | Bin 0 -> 96022 bytes .../server/src/db/index.js | 68 +++++++++-- .../server/src/services/auth-service.js | 4 +- .../server/src/services/portal-service.js | 4 +- .../server/test/db-reset-migration.test.js | 35 ++++-- .../test/portal-service-alignment.test.js | 12 +- packages/aios-management-web/src/app/App.jsx | 13 +-- packages/aios-management-web/src/brand.js | 11 ++ .../src/components/AppShell.jsx | 5 +- .../src/components/LogoMark.jsx | 2 +- .../src/pages/AgentTestPanel.jsx | 2 +- .../src/pages/AgentsPage.jsx | 2 +- .../src/pages/AiosUsersPage.jsx | 12 +- .../src/pages/DashboardPage.jsx | 2 +- .../src/pages/LargeLanguageModelsPage.jsx | 4 +- .../src/pages/LoginPage.jsx | 21 ++-- .../src/pages/SettingsPage.jsx | 3 +- .../src/pages/UserManagementPage.jsx | 4 +- .../src/pages/UsersPage.jsx | 4 +- packages/aios-management-web/src/styles.css | 60 ++++++---- .../test/integration/common.js | 4 +- .../test/integration/website-full.js | 4 +- .../test/run-browser-e2e.js | 18 +-- test/README.md | 2 + test/case/integration/playwright-flow.js | 4 +- test/case/stress/stress-flow.js | 4 +- test/prepare/stress/rebuild-stress-env.js | 2 +- test/stress/config.stress.sample.json | 4 +- .../run-100a-reuse-concurrency-matrix.js | 4 +- .../stress/run-existing-mock-lane-matrix.js | 4 +- 34 files changed, 317 insertions(+), 119 deletions(-) create mode 100644 docker/docs/openclaw-skill-context-ab-40.md create mode 100644 packages/aios-management-web/public/ikkyu-abstract.webp create mode 100644 packages/aios-management-web/src/brand.js diff --git a/README.md b/README.md index 6a7fdc4..42ac8cb 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ http://127.0.0.1:3030 默认账号: -- 用户名:`aios` -- 初始密码:`123456` +- 用户名:`admin` +- 初始密码:`admin` 首次登录后请按提示修改密码。 diff --git a/docker/docs/openclaw-skill-context-ab-40.md b/docker/docs/openclaw-skill-context-ab-40.md new file mode 100644 index 0000000..5e7e2af --- /dev/null +++ b/docker/docs/openclaw-skill-context-ab-40.md @@ -0,0 +1,108 @@ +# OpenClaw Skill 上下文污染 40 轮定向 A/B + +测试日期:2026-07-17 +模型:`deepseek-v4-flash` +旧版本:OpenClaw 2026.6.10,镜像 `sha256:1762ca310805c923cd008b36ee6d2fa6b3f6221d45d87cb4d2bb67d1a6572e7b` +新版本:OpenClaw 2026.7.1,镜像 `sha256:cf6756494ae81de8c75535375bfa8d2cbfd4a7044183d2f83ee76d62a1a9026e` + +## 结论 + +20 对 20 的定向 A/B 没有发现 OpenClaw 2026.7.1 导致 Skill 上下文污染或成功率下降。 + +- 旧版严格通过 16/20(80%)。 +- 新版严格通过 17/20(85%)。 +- 成功率差为新版 +5 个百分点;Fisher 精确检验双侧 `p=1.0`,没有统计显著差异。 +- 旧版 4 次失败全部是计划型 `block-idle`。 +- 新版 3 次失败中,2 次是计划型 `block-idle`,1 次语义完全正确但没有输出规定前缀。 +- 两个版本均未出现“继续检查前序包安装任务”或其他前序任务内容污染。 +- 两个版本的 40 轮日志均没有模型 HTTP 4xx/5xx。 + +因此,此前完整流程中新版出现的一次前序任务污染属于低频随机现象的可能性更高,当前没有证据将其归因于 OpenClaw 升级。 + +## 场景设计 + +每轮执行相同的最小长链路: + +1. 新建容器和 Docker volume。 +2. 创建模型、模板和 Agent。 +3. 上传并验证全局 Skill。 +4. 执行三类包安装任务,并由容器验证 npm、uv 和 pip 包真实可用。 +5. 执行容器重启,验证包和 Skill 持久化。 +6. 在同一 Agent 会话链路中询问 `container-skill-smoke` 的描述。 +7. 要求严格返回 `SKILL_DESCRIPTION: Container integration uploaded global skill.`。 + +文件输出、本体调用、记忆召回等无关阶段被跳过,以便把失败归因集中在“前序安装任务 → 重启 → Skill 描述”链路。 + +两个版本使用相同模型、Base URL、凭据、提示、超时和主机。旧、新版本并行执行,使用独立端口、容器和 volume。 + +## 结果 + +| 指标 | 2026.6.10 | 2026.7.1 | +| --- | ---: | ---: | +| 样本数 | 20 | 20 | +| 严格通过 | 16 | 17 | +| 严格失败 | 4 | 3 | +| 严格成功率 | 80% | 85% | +| 计划型 `block-idle` | 4 | 2 | +| 语义正确但格式失败 | 0 | 1 | +| 前序任务内容污染 | 0 | 0 | +| 模型 HTTP 4xx/5xx | 0 | 0 | + +Wilson 95% 置信区间: + +- 旧版成功率:58.4%~91.9%。 +- 新版成功率:64.0%~94.8%。 + +两区间高度重叠。按当前样本量,新旧成功率差异的近似 95% 区间约为 -18.5~+28.5 个百分点,不能宣称新版更好,也不能宣称新版更差。 + +## 失败详情 + +### 2026.6.10 + +4 次失败都是模型宣布下一步动作后直接结束: + +- `Let me check the skill.` +- `Let me read the skill's SKILL.md to confirm.` +- `Let me read the SKILL.md for that skill.` +- `Let me read the SKILL.md for the container-skill-smoke skill.` + +事件均以 `block-idle` 结束,没有继续读取 Skill 或返回最终结果。 + +### 2026.7.1 + +2 次计划型提前结束: + +- `Let me check the skill's metadata and SKILL.md.` +- `I already have the skill metadata visible in my available skills list. Reading its SKILL.md for confirmation:` + +另 1 次已经正确回答 Skill 描述,但没有遵守固定输出协议,附带了额外说明,因此严格断言失败。按业务语义口径,新版为 18/20(90%);旧版仍为 16/20(80%)。该口径同样不足以形成显著版本差异。 + +## 性能 + +采用中位数,降低外部 API 和并发抖动的影响: + +| 指标中位数 | 2026.6.10 | 2026.7.1 | +| --- | ---: | ---: | +| 首次启动 | 9,384ms | 15,932ms | +| 第二次启动 | 12,794ms | 13,402ms | +| Agent 创建 | 10,217ms | 7,984ms | +| Agent 回声 | 5,570ms | 5,000ms | +| 包安装任务 | 7,606ms | 14,468ms | +| 重启 | 13,048ms | 11,168ms | +| 成功的 Skill 描述 | 7,746ms | 7,130ms | + +首次启动在新版仍明显更慢,与完整流程对比结论一致。新版包安装任务中位数更长,但模型的包安装回复常先于后台工具完成,单独看该字段不能等同于实际安装总耗时。Skill 描述本身没有性能退化。 + +## 判断 + +1. Skill 场景的计划型 `block-idle` 在升级前已经存在,且本次旧版本样本中出现次数更多。 +2. 40 轮没有复现任何前序包安装内容污染,不能把之前单次污染归因于 2026.7.1。 +3. 新版的严格成功率没有下降,但 20/组仍不足以证明 5 个百分点差异是真实改善。 +4. 不需要因为该问题回滚 OpenClaw;应该修复通用的 Agent 终止状态和输出协议。 + +## 建议 + +- 将“准备读取/检查 Skill”且没有工具结果的 `block-idle` 判为未完成,自动续跑一次。 +- 对 Skill 描述从已加载元数据中直接生成结构化响应,避免模型自行控制固定前缀。 +- 保留该定向场景作为版本升级回归测试;建议发布候选版本运行 50~100 轮,并设置失败率控制线,而不是要求零随机失败。 +- 当前可采用的基线是:严格成功率约 80%~85%,这仍不适合作为无需重试的生产链路。 diff --git a/packages/aios-management-web/README.md b/packages/aios-management-web/README.md index a9550bf..334960f 100644 --- a/packages/aios-management-web/README.md +++ b/packages/aios-management-web/README.md @@ -70,8 +70,8 @@ npm run test:full ## 默认账号 -- 用户名:`aios` -- 初始密码:`123456` +- 用户名:`admin` +- 初始密码:`admin` 首次使用默认密码登录后,系统会要求管理员修改密码。 diff --git a/packages/aios-management-web/index.html b/packages/aios-management-web/index.html index 03ac3ac..c83e460 100644 --- a/packages/aios-management-web/index.html +++ b/packages/aios-management-web/index.html @@ -4,7 +4,7 @@ - 数字员工控制台 + ikkyu-agent
diff --git a/packages/aios-management-web/public/ikkyu-abstract.webp b/packages/aios-management-web/public/ikkyu-abstract.webp new file mode 100644 index 0000000000000000000000000000000000000000..a62c7ba36c84b1056ff287a1b6636e19b8f1cdd3 GIT binary patch literal 96022 zcmV(xKL{=Y z31w@yyj!1g#INlC@$agN@gT)rkMcvtzFeQi>HN$e{X6zP-|{^wAQPqs5_L(~I}eGj zivPLekN5xjyk>n@{a>&j^6%^)?mpfAbpPG&Rq>zpy@7rizKkH@}Ky>$$TtvkAWW3```JW`(NSyaX-W5kHP=Z z^OWcx{J-XZI6h|oy8qq&oBfBu@6_M;zd!zf|6BiN|Jr+K{_cCd{p0ly{crt`?XSIe z{7>Caci-dx=6eHwQvZwp|JQ&2|GF3N5C8vhf9pT=|Nr|&|LFbN|GVhp?+?4L z{15;C^gsXn0DtuM!2SXMql|z5U-|D*A1wQ6{zLLd);__X!+)XwmHtQPfA=r>Jp24_ z`G2;8P(1Mu}mmxeYdt#4)h9uyKpF>Wmu-r4z5c1P`G$K8^9;7Jz337~Y5p18 zhpP2-VzQG(lu&MG7#6-Vl6{yx75R-EVZ`-9yA;=^kr79a;vsM`F;`PZ-vd?=frTsp zp|!lDP_wKN(Vcd011d=Dpx9`=*e=@iCX91I*6Z-pPeg#VvXKSv>8K^$pr*}_lObaVte<}UHMcafA1W60 zKMN6lhd#a`1%cjh{)NQut~PH7UCd76%iHCGe$;(eFWP(2FLo+aI-y!QwO9$YdH{3k zik}q6(WiCBQ9`IPKz>+7DIdBy<+0C1kjd~DQ$K065)aeMA1Sz{7&nd0tu94_)16xt z#Y0{nG6oycw>sfyK;-w?_5^jG8mdCq^p$QC1vcUAz}{)GO=XU@4me+9e(~_ja;`I@G1u)xM76TK#;ye{mLjDvbuxA%7N4~V&m!~~`g}Fx=N*3L) z+VH^CJT7AuWS~V#u zoI?4}69(uMSnRpZ*aezX`4|y#fEe8usvoE){r$Z_4gdbWoUe3dwO3tRAkktphpMYl znRgYB4^zhR6MP{TbIMK~!mZ-7Z7zbArhXyk=-ad#M2#o{FSBP|K;KLErH!?Me zzcS{;bZ5QX8IoNz86i@#?1^mJN#Oz7ju~^dL#rQ$waN$DmMZ@ z{4~~lX?mV52rQx7Vpbg{Qz%&^Yo2}I(n&v+?C^3B_-)GJYAXCY;SX&p(LKg^W!-M> zKJks+V9(`2T$M7LGjqeYPZ2yktM3yOD4b4oW=QYQQ!T)bm1lB70wF9hTki)b&VXG_w?RE5I_9At_GWUti1QV@*LA?z<0S82ZIE05~VdW z1r8sT&vEwAEaq=_g1^5}s%Hz0tQSB! zDN+>iCMLR|sFkLyc|#QcfA72{ytXg5DzJDtz#>z*K_LJ9nwgT{aao#25Z-6gU#CPP z1VtD$7s66%*Jc2L9g(LnbNIBHskCG5N_%)($9!Sc1q@_e zW#I?4sL_<|Gd{j*Sf^=7aD%#Ohzc2-jqz$_=CAK^=V}127lTY_+qpyq8y$lAjED7l+E^tGyRU@n82<;teqXQU_4k_eFeSNh(7v0!w@O{_M?+%N%E zqOftSu+!yjdDv&ev(hXwNG24<2SM%8VU_oni_p(_QkWqf`B*8O5d(|;GlCX%y!^60 zFLgDmZThb@i?C$f2|0!MI<%4#5Sv=C$$)*uYYeDtyo*|m-|LZP`I0=XbOUh=C?WsP zSMh0C&})5^L-@*F&J(ee!QnQ?swI!2w;SQ|e}$&;g0@W>V}CLzR5m5x9HO!;iu;ME zrL!e)0J|{;R^P_*dHEorLhy17|NNW8{Jsl{m8M0N0M%L9Gpr~C^EKjSDabk&MZf?o zB4*?$ME-e`9~t>&ExoO%qhuiTdxVf*4Z8ZP60!-6M7|5|0a;Kd>#PCFZS(_{E%@j}q$2yCB(T%6*-w6xxg@5|W|#f&xR;zkMDWtOHXB}}Q{s%V>@2j%VXgQk z)B)O%L&D%~^tMDU?4Ifs6pO9f=PhJEDTxr%la3e} z|K2Q8rI44T3D+ZnG#$akmEyI47Fds
  • 0F(ZauJQQ%DGk)?sTTdd^ZW|p0pQsfP( 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 { const dbFile = path.join(dataDir, "management-console.db"); setupV7BrandingDatabase(dbFile, { portalName: "客户自定义控制台", - adminDisplayName: "运维管理员" + adminDisplayName: "运维管理员", + adminPassword: "custom-secret" }); process.env.AIOS_WEB_DATA_DIR = dataDir; @@ -337,10 +349,13 @@ it("keeps customized branding while applying core row checks", async () => { const { db } = await import(moduleUrl.href); const settings = db.prepare("SELECT portal_name FROM settings WHERE id = 1").get(); - const admin = db.prepare("SELECT display_name FROM users WHERE username = 'aios'").get(); + const admin = db.prepare("SELECT username, display_name, password_hash FROM users WHERE is_builtin = 1").get(); expect(settings.portal_name).toBe("客户自定义控制台"); + expect(admin.username).toBe("admin"); expect(admin.display_name).toBe("运维管理员"); + expect(verifyPassword("custom-secret", admin.password_hash)).toBe(true); + expect(verifyPassword("admin", admin.password_hash)).toBe(false); restoreDataDir(previousDataDir); }); @@ -665,7 +680,7 @@ it("migrates v4 settings through uploaded logo columns without dropping data", a expect(settings).toEqual({ portal_name: "AIOS", brand_subtitle: "Test", - theme_color: "#07c160", + theme_color: "#205b57", logo_mime_type: "", logo_data: null }); diff --git a/packages/aios-management-web/server/test/portal-service-alignment.test.js b/packages/aios-management-web/server/test/portal-service-alignment.test.js index f384ac4..1e86481 100644 --- a/packages/aios-management-web/server/test/portal-service-alignment.test.js +++ b/packages/aios-management-web/server/test/portal-service-alignment.test.js @@ -399,9 +399,9 @@ it("updates portal settings without touching uploaded logo data", () => { const calls = []; let settings = { id: 1, - portal_name: "数字员工控制台", - brand_subtitle: "Your Company Name", - theme_color: "#07c160", + portal_name: "ikkyu-agent", + brand_subtitle: "智能体管理与运营平台", + theme_color: "#205b57", logo_mime_type: "image/png", has_logo: true, created_at: "2026-05-26T00:00:00.000Z", @@ -447,9 +447,9 @@ it("updates and clears uploaded portal logo", () => { const calls = []; let settings = { id: 1, - portal_name: "数字员工控制台", - brand_subtitle: "Your Company Name", - theme_color: "#07c160", + portal_name: "ikkyu-agent", + brand_subtitle: "智能体管理与运营平台", + theme_color: "#205b57", logo_mime_type: "", has_logo: false, created_at: "2026-05-26T00:00:00.000Z", diff --git a/packages/aios-management-web/src/app/App.jsx b/packages/aios-management-web/src/app/App.jsx index 225e33b..71d775f 100644 --- a/packages/aios-management-web/src/app/App.jsx +++ b/packages/aios-management-web/src/app/App.jsx @@ -15,6 +15,7 @@ import { ConfigProvider, Form, Input, Modal, Spin, message } from "antd"; import zhCN from "antd/locale/zh_CN"; import { api, authTokenStore } from "./api-client.js"; +import { BRAND_NAME, BRAND_THEME_COLOR, DEFAULT_PORTAL_SETTINGS } from "../brand.js"; import { AppShell } from "../components/AppShell.jsx"; import { DigitalEmployeeIcon } from "../components/DigitalEmployeeIcon.jsx"; import { AgentsPage } from "../pages/AgentsPage.jsx"; @@ -33,13 +34,7 @@ import { } from "../utils/startupSyncGate.js"; import { waitForGatewayRestart } from "../utils/serverRestartPolling.js"; -const DEFAULT_THEME_COLOR = "#07c160"; -const DEFAULT_PORTAL_SETTINGS = { - portal_name: "数字员工控制台", - brand_subtitle: "Your Company Name", - theme_color: DEFAULT_THEME_COLOR, - has_logo: false -}; +const DEFAULT_THEME_COLOR = BRAND_THEME_COLOR; function buildLogoSrc(settings) { return settings?.has_logo ? `/api/portal/logo?t=${encodeURIComponent(settings.updated_at || Date.now())}` : ""; @@ -440,7 +435,7 @@ export default function App() { onServerRestart={async (updateStatus) => { try { await api.post("/api/settings/server-restart", {}); - updateStatus?.("重启指令已发送,正在等待数字员工平台恢复可用..."); + updateStatus?.(`重启指令已发送,正在等待 ${BRAND_NAME} 恢复可用...`); await waitForGatewayRestart({ getStatus: (timeoutMs) => api.post( `/api/settings/server-status?audit=false&timeout_ms=${timeoutMs}`, @@ -449,7 +444,7 @@ export default function App() { ), onStatus: updateStatus }); - messageApi.success("数字员工平台已恢复可用"); + messageApi.success(`${BRAND_NAME} 已恢复可用`); } catch (error) { const normalized = api.normalizeError(error, "重启服务失败"); messageApi.error(normalized.message || "重启服务失败"); diff --git a/packages/aios-management-web/src/brand.js b/packages/aios-management-web/src/brand.js new file mode 100644 index 0000000..33d8836 --- /dev/null +++ b/packages/aios-management-web/src/brand.js @@ -0,0 +1,11 @@ +export const BRAND_NAME = "ikkyu-agent"; +export const BRAND_SUBTITLE = "智能体管理与运营平台"; +export const BRAND_THEME_COLOR = "#205b57"; +export const BRAND_VISUAL_URL = "/ikkyu-abstract.webp"; + +export const DEFAULT_PORTAL_SETTINGS = { + portal_name: BRAND_NAME, + brand_subtitle: BRAND_SUBTITLE, + theme_color: BRAND_THEME_COLOR, + has_logo: false +}; diff --git a/packages/aios-management-web/src/components/AppShell.jsx b/packages/aios-management-web/src/components/AppShell.jsx index 6dab5b5..48ab67a 100644 --- a/packages/aios-management-web/src/components/AppShell.jsx +++ b/packages/aios-management-web/src/components/AppShell.jsx @@ -7,13 +7,14 @@ import { } from "@ant-design/icons"; import { Button, Layout, Menu, Space, Typography } from "antd"; +import { BRAND_THEME_COLOR } from "../brand.js"; import { LogoMark } from "./LogoMark.jsx"; const { Header, Sider, Content } = Layout; const { Text } = Typography; -const DEFAULT_THEME_COLOR = "#07c160"; +const DEFAULT_THEME_COLOR = BRAND_THEME_COLOR; -function normalizeHexColor(color, fallback = "#07c160") { +function normalizeHexColor(color, fallback = BRAND_THEME_COLOR) { if (typeof color !== "string") { return fallback; } diff --git a/packages/aios-management-web/src/components/LogoMark.jsx b/packages/aios-management-web/src/components/LogoMark.jsx index bb2b783..2140965 100644 --- a/packages/aios-management-web/src/components/LogoMark.jsx +++ b/packages/aios-management-web/src/components/LogoMark.jsx @@ -15,7 +15,7 @@ export function LogoMark({ src, className }) { {normalizedSrc && !failed ? ( Logo setFailed(true)} /> ) : ( - + )} ); diff --git a/packages/aios-management-web/src/pages/AgentTestPanel.jsx b/packages/aios-management-web/src/pages/AgentTestPanel.jsx index 6e2aa35..5be5f9b 100644 --- a/packages/aios-management-web/src/pages/AgentTestPanel.jsx +++ b/packages/aios-management-web/src/pages/AgentTestPanel.jsx @@ -79,7 +79,7 @@ export function AgentTestPanel({ agent }) { placement: "start", avatar: { icon: , - style: { background: "var(--portal-theme-color, #07c160)" } + style: { background: "var(--portal-theme-color, #205b57)" } } } }), []); diff --git a/packages/aios-management-web/src/pages/AgentsPage.jsx b/packages/aios-management-web/src/pages/AgentsPage.jsx index fc64c07..74979f0 100644 --- a/packages/aios-management-web/src/pages/AgentsPage.jsx +++ b/packages/aios-management-web/src/pages/AgentsPage.jsx @@ -919,7 +919,7 @@ export function AgentsPage({ refreshKey = 0 }) { {permissionAgent?.slug || "-"} - 直接输入用户名后回车即可新增并分配,输入时会优先联想已有数字员工平台用户。 + 直接输入用户名后回车即可新增并分配,输入时会优先联想已有 ikkyu-agent 用户。 diff --git a/packages/aios-management-web/src/pages/AiosUsersPage.jsx b/packages/aios-management-web/src/pages/AiosUsersPage.jsx index 8f20d0a..8c9d8bb 100644 --- a/packages/aios-management-web/src/pages/AiosUsersPage.jsx +++ b/packages/aios-management-web/src/pages/AiosUsersPage.jsx @@ -54,7 +54,7 @@ export function AiosUsersPage() { load()} />} + title={ load()} />} extra={( - 默认管理员账号为 aios,初始密码为 123456。 + 默认管理员账号和初始密码均为 admin。 diff --git a/packages/aios-management-web/src/pages/SettingsPage.jsx b/packages/aios-management-web/src/pages/SettingsPage.jsx index 11a7e19..e2ccde3 100644 --- a/packages/aios-management-web/src/pages/SettingsPage.jsx +++ b/packages/aios-management-web/src/pages/SettingsPage.jsx @@ -17,13 +17,14 @@ import { } from "@ant-design/icons"; import { DeleteActionButton } from "../components/DeleteActionButton.jsx"; +import { BRAND_THEME_COLOR } from "../brand.js"; import { LogoMark } from "../components/LogoMark.jsx"; import { SystemLogDownloadPanel } from "../components/SystemLogDownloadPanel.jsx"; const { Text } = Typography; const LOGO_ACCEPT = ".jpeg,.jpg,.png,.bmp,image/jpeg,image/png,image/bmp,image/x-ms-bmp"; const MAX_LOGO_BYTES = 1024 * 1024; -const DEFAULT_THEME_COLOR = "#07c160"; +const DEFAULT_THEME_COLOR = BRAND_THEME_COLOR; const TOKEN_MASK_LENGTH = 10; const DEFAULT_COMMAND_PARAMS = "{}"; const commandResultBlockStyle = { diff --git a/packages/aios-management-web/src/pages/UserManagementPage.jsx b/packages/aios-management-web/src/pages/UserManagementPage.jsx index acd23da..35aeacc 100644 --- a/packages/aios-management-web/src/pages/UserManagementPage.jsx +++ b/packages/aios-management-web/src/pages/UserManagementPage.jsx @@ -12,12 +12,12 @@ export function UserManagementPage({ currentUser }) { items={[ { key: "aiosUsers", - label: "数字员工平台用户", + label: "ikkyu-agent 用户", children: }, { key: "users", - label: "数字员工平台管理员", + label: "ikkyu-agent 管理员", children: } ]} diff --git a/packages/aios-management-web/src/pages/UsersPage.jsx b/packages/aios-management-web/src/pages/UsersPage.jsx index 790dc7a..197a5c3 100644 --- a/packages/aios-management-web/src/pages/UsersPage.jsx +++ b/packages/aios-management-web/src/pages/UsersPage.jsx @@ -7,7 +7,7 @@ import { CardTitleWithReload } from "../components/CardTitleWithReload.jsx"; import { DeleteActionButton } from "../components/DeleteActionButton.jsx"; function isBuiltinAdmin(row) { - return row.is_builtin || row.username === "aios"; + return row.is_builtin || row.username === "admin"; } function renderUserStatus(status) { @@ -59,7 +59,7 @@ export function UsersPage({ currentUser }) { load()} />} + title={ load()} />} extra={(