upgrade openclaw to 7.1-2 (#1236)

This commit is contained in:
paisley
2026-08-12 11:53:53 +08:00
committed by GitHub
parent 68705d96de
commit d5168842de
11 changed files with 271 additions and 53 deletions
+17 -1
View File
@@ -36,7 +36,10 @@ import { prependPathEntry } from '../utils/env-path';
import { copyPluginFromNodeModules, fixupPluginManifest, cpSyncSafe, buildCandidateSources, repairTrustedOfficialPluginInstallRecords, removeTrustedOfficialPluginInstallRecord, resolvePluginNpmPackagePath } from '../utils/plugin-install';
import { safeRmSync } from '../utils/safe-fs';
import { CLAWX_OPENAI_IMAGE_PROVIDER_KEY } from '../utils/openclaw-image-relay-constants';
import { ensureOpenClaw2026_7_1UpgradeSnapshot } from '../utils/openclaw-upgrade-snapshot';
import {
ensureOpenClaw2026_7_1UpgradeSnapshot,
quarantineLegacyUpdateCheckState,
} from '../utils/openclaw-upgrade-snapshot';
import { stripSystemdSupervisorEnv } from './config-sync-env';
import { cleanupAgentsSymlinkedSkills, cleanupStalePluginRuntimeDeps } from './skills-symlink-cleanup';
import {
@@ -651,6 +654,19 @@ export async function prepareGatewayLaunchContext(port: number): Promise<Gateway
}
});
await measureAsync(timingsMs, 'legacyUpdateCheckCleanupMs', async () => {
try {
const cleanup = await quarantineLegacyUpdateCheckState();
if (cleanup.status === 'quarantined') {
logger.info(
`[upgrade] Quarantined conflicting legacy update-check state: ${cleanup.sourcePath}${cleanup.backupPath}`,
);
}
} catch (error) {
logger.warn('[upgrade] Failed to quarantine legacy update-check state:', error);
}
});
const appSettings = await measureAsync(timingsMs, 'settingsMs', getAllSettings);
const prelaunchSummary = await measureAsync(timingsMs, 'prelaunchSyncMs', async () => (
await syncGatewayConfigBeforeLaunch(appSettings, openclawDir)
+4
View File
@@ -735,6 +735,10 @@ export class GatewayManager extends EventEmitter {
logger.info('Gateway ready fallback RPC router probe succeeded');
this.resetGatewayReadyFallback();
this.setStatus({ gatewayReady: true });
// A fast Gateway can emit gateway.ready before the WebSocket client is
// attached. A successful router probe is equivalent readiness, so it
// must also complete the one-time migration snapshot lifecycle.
void this.cleanupOpenClawUpgradeSnapshot();
}
} catch (error) {
this.capabilityMonitor.recordCoreProbe({
@@ -1,5 +1,6 @@
import { chmod, copyFile, lstat, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
import { basename, dirname, join, relative, resolve } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { resolveOpenClawConfigPath, resolveOpenClawStateDir } from './paths';
const UPGRADE_ID = 'openclaw-2026.7.1';
@@ -23,6 +24,12 @@ export type OpenClawUpgradeSnapshotCleanupResult = {
snapshotDir: string;
};
export type LegacyUpdateCheckCleanupResult = {
status: 'quarantined' | 'missing' | 'deferred';
sourcePath: string;
backupPath?: string;
};
type SnapshotOptions = {
stateDir?: string;
configPath?: string;
@@ -57,6 +64,44 @@ async function copyFileIfPresent(source: string, destination: string, copied: st
copied.push(destination);
}
async function pathExists(path: string): Promise<boolean> {
try {
await lstat(path);
return true;
} catch {
return false;
}
}
async function resolveAvailableBackupPath(basePath: string): Promise<string> {
if (!await pathExists(basePath)) return basePath;
const timestamp = Date.now();
for (let suffix = 0; suffix < 100; suffix += 1) {
const candidate = `${basePath}.${timestamp}${suffix === 0 ? '' : `-${suffix}`}`;
if (!await pathExists(candidate)) return candidate;
}
throw new Error(`Could not allocate backup path for ${basePath}`);
}
function hasCanonicalUpdateCheckState(sqlitePath: string): boolean {
let db: DatabaseSync | undefined;
try {
db = new DatabaseSync(sqlitePath, { readOnly: true });
const row = db.prepare(`
SELECT 1 AS present
FROM update_check_state
WHERE state_key = ?
LIMIT 1
`).get('default') as { present?: number } | undefined;
return row?.present === 1;
} catch {
return false;
} finally {
db?.close();
}
}
async function copyTree(
sourceRoot: string,
destinationRoot: string,
@@ -161,6 +206,46 @@ export async function ensureOpenClaw2026_7_1UpgradeSnapshot(
}
}
/**
* OpenClaw 2026.7.1 refuses Gateway readiness when the legacy update-check JSON
* differs from an existing canonical SQLite row. The JSON contains updater
* bookkeeping only, and upstream would archive it when both copies match. Once
* SQLite has the canonical row, move the legacy file out of the active state
* root so a harmless mismatch cannot trap startup or an ineffective doctor
* retry loop. If SQLite has no row yet, leave the JSON for upstream to import.
*/
export async function quarantineLegacyUpdateCheckState(
options: Pick<SnapshotOptions, 'stateDir'> = {},
): Promise<LegacyUpdateCheckCleanupResult> {
const stateDir = resolve(options.stateDir ?? resolveOpenClawStateDir());
const sourcePath = join(stateDir, 'update-check.json');
let sourceInfo;
try {
sourceInfo = await lstat(sourcePath);
} catch {
return { status: 'missing', sourcePath };
}
if (!sourceInfo.isFile() && !sourceInfo.isSymbolicLink()) {
return { status: 'deferred', sourcePath };
}
const sqlitePath = join(stateDir, 'state', 'openclaw.sqlite');
if (!hasCanonicalUpdateCheckState(sqlitePath)) {
return { status: 'deferred', sourcePath };
}
const backupDir = join(stateDir, 'backups');
await mkdir(backupDir, { recursive: true, mode: SNAPSHOT_DIR_MODE });
const backupPath = await resolveAvailableBackupPath(
join(backupDir, `clawx-${UPGRADE_ID}-legacy-update-check.json`),
);
await rename(sourcePath, backupPath);
if (sourceInfo.isFile()) {
await chmod(backupPath, SNAPSHOT_FILE_MODE);
}
return { status: 'quarantined', sourcePath, backupPath };
}
/**
* Removes the one-time OpenClaw 2026.7.1 pre-migration snapshot after Gateway
* startup succeeds so duplicated config/auth/SQLite secrets do not linger.
@@ -1,6 +1,6 @@
# OpenClaw Config Delivery
ClawX bundles OpenClaw 2026.7.1. OpenClaw owns the field-level decision between a no-op snapshot update, hot application, subsystem restart, and in-process Gateway restart.
ClawX bundles OpenClaw 2026.7.1-2. OpenClaw owns the field-level decision between a no-op snapshot update, hot application, subsystem restart, and in-process Gateway restart.
Provider, Agent, Channel, skill, proxy, image-generation, and plugin-install helpers express config changes as mutators. One Main-owned coordinator owns selection of the authoritative baseline and the commit:
@@ -16,6 +16,8 @@ Gateway WebSocket tracing must redact the complete serialized `raw` payload for
Coordinator-backed reads follow the same authority rule: prefer the runtime-shaped `config.get.config` object while Gateway is running and use JSON5 file parsing while it is not. Compound views derive all config-backed fields from one snapshot.
OpenClaw 2026.7.1 keeps auth-profile SQLite snapshots in memory. After a completed auth-store write batch, ClawX calls `secrets.reload` once when Gateway is running. `config.set` does not replace this refresh. Agent `models.json` needs no explicit RPC because OpenClaw re-reads it when its file fingerprint changes.
OpenClaw 2026.7.1-2 keeps auth-profile SQLite snapshots in memory. After a completed auth-store write batch, ClawX calls `secrets.reload` once when Gateway is running. `config.set` does not replace this refresh. Agent `models.json` needs no explicit RPC because OpenClaw re-reads it when its file fingerprint changes.
Before launch, upgrade compatibility cleanup checks the canonical `state/openclaw.sqlite` update-check row. If it exists, the SQLite row is authoritative and any legacy root `update-check.json` is moved with restrictive permissions under `backups/`; otherwise the JSON remains in place for OpenClaw to import. This cleanup runs after the one-time upgrade snapshot and prevents harmless updater-bookkeeping differences from blocking Gateway readiness or triggering an ineffective doctor retry. The snapshot is removed after either the native ready event or a successful RPC-router readiness fallback, covering the race where a fast Gateway emits readiness before ClawX attaches its WebSocket client.
Full ClawX process replacement remains necessary after a successful coordinator commit when values are injected only at process creation, including proxy environment changes, or for explicit manual lifecycle and health/crash recovery. OpenClaw config categories must not be duplicated as a ClawX restart whitelist.
@@ -0,0 +1,56 @@
---
id: upgrade-openclaw-2026-7-1-2
title: Upgrade the bundled OpenClaw runtime to 2026.7.1-2
scenario: gateway-backend-communication
taskType: runtime-bridge
intent: Apply the OpenClaw 2026.7.1 correction releases without regressing ClawX channels, providers, models, ACP chat, or packaged runtime behavior.
touchedAreas:
- package.json
- pnpm-lock.yaml
- electron/gateway/config-sync.ts
- electron/gateway/manager.ts
- electron/utils/openclaw-upgrade-snapshot.ts
- tests/unit/gateway-ready-fallback.test.ts
- tests/unit/openclaw-bundle-config.test.ts
- tests/unit/openclaw-upgrade-snapshot.test.ts
- harness/reference/openclaw-config-delivery.md
- harness/specs/tasks/upgrade-openclaw-2026-7-1.md
- harness/specs/tasks/upgrade-openclaw-2026-7-1-2.md
expectedUserBehavior:
- A direct ClawX 0.5.1 upgrade from OpenClaw 2026.6.10 preserves configuration, authentication, sessions, ClawX-managed selected models, channel credentials, and managed channel plugin identities through the inherited 2026.7.1 migration.
- Manual catalog-only model references require explicit upgrade preflight because the upstream unconfigured global model catalog is smaller in 2026.7.1 than in 2026.6.10; explicit ClawX-managed provider models are not dependent on that catalog entry remaining built in.
- Existing configuration, authentication, sessions, models, and channel credentials remain usable after upgrading from OpenClaw 2026.7.1.
- The supported ClawX channel catalog and effective plugin identities remain unchanged.
- Official managed plugin updates tolerate repaired npm lock metadata and singleton-array npm view responses.
- Codex turns continue to their authoritative terminal result after progress replies.
- Legacy migration residue, Memory Core derived-sidecar conflicts, and guarded WSL EROFS permission results do not cause avoidable Gateway startup failures.
- When canonical SQLite update-check state already exists, conflicting legacy update-check JSON is backed up outside the active state root before launch instead of causing a doctor retry loop.
requiredProfiles:
- fast
- comms
requiredTests:
- tests/unit/gateway-ready-fallback.test.ts
- tests/unit/openclaw-bundle-config.test.ts
- tests/unit/openclaw-upgrade-snapshot.test.ts
- tests/unit/plugin-install.test.ts
- tests/unit/plugin-install-index.test.ts
- tests/unit/channel-config.test.ts
- tests/unit/openclaw-auth.test.ts
- tests/unit/acp-chat-service.test.ts
- tests/unit/gateway-startup-orchestrator.test.ts
acceptance:
- The OpenClaw runtime is pinned to 2026.7.1-2 and resolves @openclaw/ai 2026.7.1-2.
- External channel plugin package versions and ClawX's supported channel catalog remain unchanged because the correction release does not change channel APIs or manifests.
- Provider and model configuration behavior remains unchanged because the correction release does not change provider or model catalog sources.
- The bundled-runtime patch and pruning pipeline succeeds against OpenClaw 2026.7.1-2.
- Prelaunch preserves legacy update-check JSON for upstream import when SQLite has no canonical row, and quarantines it with restrictive permissions when SQLite is already authoritative.
- The pre-migration snapshot is removed after either the native ready event or an equivalent successful RPC-router readiness fallback, so a missed early event does not leave duplicated auth/SQLite secrets behind.
- Type checks, targeted channel/provider/ACP tests, communication regression checks, and harness validation pass.
docs:
required: false
---
Use this task spec for the correction-release upgrade from OpenClaw 2026.7.1
to 2026.7.1-2. It inherits the runtime and migration compatibility work captured
in `upgrade-openclaw-2026-7-1.md` and focuses on proving that the correction
release does not widen ClawX's channel or model surface.
@@ -29,6 +29,7 @@ touchedAreas:
- tests/e2e/cron-run-live-status.spec.ts
- tests/unit/gateway-startup-recovery.test.ts
- tests/unit/gateway-startup-orchestrator.test.ts
- tests/unit/gateway-ready-fallback.test.ts
- tests/unit/openclaw-cli.test.ts
- tests/unit/openclaw-bundle-config.test.ts
- tests/unit/openclaw-upgrade-snapshot.test.ts
@@ -39,11 +40,13 @@ touchedAreas:
- README.zh-CN.md
- README.ja-JP.md
- README.ru-RU.md
- harness/reference/openclaw-config-delivery.md
- harness/specs/scenarios/gateway-backend-communication.md
- harness/specs/rules/acp-chat-state-and-history.md
- harness/specs/tasks/upgrade-openclaw-2026-7-1.md
- harness/specs/tasks/upgrade-openclaw-2026-7-1-2.md
expectedUserBehavior:
- Existing OpenClaw 2026.6.10 configuration, authentication, sessions, and channel credentials remain usable after upgrade, with a one-time pre-migration snapshot of migration-critical config/auth/SQLite state that is removed after Gateway startup succeeds.
- Existing ClawX-managed OpenClaw 2026.6.10 configuration, authentication, sessions, selected provider models, and channel credentials remain usable after upgrade, with a one-time pre-migration snapshot of migration-critical config/auth/SQLite state that is removed after Gateway startup succeeds.
- ClawX reconciles old managed channel-plugin install records with its current mirrored extensions, removes records for unconfigured mirrors, and links declared `openclaw` peers to the bundled runtime before OpenClaw's post-core payload smoke check.
- ClawX starts and communicates with the bundled OpenClaw 2026.7.1 Gateway, including migration and control-plane safe-mode startup states.
- ClawX registers the compatibility-patched WeCom mirror as a local-path install with static channel metadata so OpenClaw startup migration does not replace it with the raw mismatched npm package.
@@ -79,6 +82,7 @@ acceptance:
- Configured mirrored plugins that declare an `openclaw` peer have a runtime link to the current bundled OpenClaw package before migration validation; stale install records for unconfigured mirrors are removed so missing directories cannot block startup.
- Gateway recovery performs at most one doctor repair per startup flow and does not retry fatal runtime, EX_CONFIG, invalid migration, or active migration-lease failures indefinitely.
- Electron Main reads current cron history through Gateway `cron.runs`, retains legacy JSONL as a compatibility fallback, and supplements only empty cron ACP replay in memory without replacing non-empty replay.
- ClawX-managed selected models remain explicit in provider configuration, but upgrade preflight treats manual catalog-only model references separately: OpenClaw's unconfigured `models list --all` catalog changes from 140 entries in 2026.6.10 to 86 in 2026.7.1, removing the built-in Venice, Fireworks, Tencent TokenHub, and Z.AI catalog groups plus several older Moonshot entries.
- ACP 1.1 type checks, targeted runtime tests, communication regression checks, and harness validation pass.
docs:
required: true
+1 -1
View File
@@ -175,7 +175,7 @@
"monaco-editor": "^0.55.1",
"mpg123-decoder": "^1.0.3",
"ms": "^2.1.3",
"openclaw": "2026.7.1",
"openclaw": "2026.7.1-2",
"opusscript": "^0.1.1",
"pdfjs-dist": "^5.7.284",
"playwright-core": "1.59.1",
+33 -45
View File
@@ -65,7 +65,7 @@ importers:
version: 1.3.7
'@larksuite/openclaw-lark':
specifier: 2026.7.9
version: 2026.7.9(openclaw@2026.7.1(encoding@0.1.13))
version: 2026.7.9(openclaw@2026.7.1-2(encoding@0.1.13))
'@larksuiteoapi/node-sdk':
specifier: ^1.61.1
version: 1.62.0
@@ -74,13 +74,13 @@ importers:
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@openclaw/discord':
specifier: 2026.7.1
version: 2026.7.1(openclaw@2026.7.1(encoding@0.1.13))
version: 2026.7.1(openclaw@2026.7.1-2(encoding@0.1.13))
'@openclaw/qqbot':
specifier: 2026.7.1
version: 2026.7.1(openclaw@2026.7.1(encoding@0.1.13))
version: 2026.7.1(openclaw@2026.7.1-2(encoding@0.1.13))
'@openclaw/whatsapp':
specifier: 2026.7.1
version: 2026.7.1(openclaw@2026.7.1(encoding@0.1.13))
version: 2026.7.1(openclaw@2026.7.1-2(encoding@0.1.13))
'@playwright/test':
specifier: ^1.56.1
version: 1.59.0
@@ -125,7 +125,7 @@ importers:
version: 0.34.48
'@soimy/dingtalk':
specifier: 3.6.6
version: 3.6.6(openclaw@2026.7.1(encoding@0.1.13))
version: 3.6.6(openclaw@2026.7.1-2(encoding@0.1.13))
'@streamdown/cjk':
specifier: ^1.0.3
version: 1.0.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.4)(unified@11.0.5)
@@ -140,7 +140,7 @@ importers:
version: 1.1.0
'@tencent-weixin/openclaw-weixin':
specifier: ^2.4.6
version: 2.4.6(openclaw@2026.7.1(encoding@0.1.13))
version: 2.4.6(openclaw@2026.7.1-2(encoding@0.1.13))
'@testing-library/jest-dom':
specifier: ^6.9.1
version: 6.9.1
@@ -173,7 +173,7 @@ importers:
version: 5.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.9.0))
'@wecom/wecom-openclaw-plugin':
specifier: 2026.7.2
version: 2026.7.2(openclaw@2026.7.1(encoding@0.1.13))
version: 2026.7.2(openclaw@2026.7.1-2(encoding@0.1.13))
'@whiskeysockets/baileys':
specifier: 7.0.0-rc.9
version: 7.0.0-rc.9(audio-decode@2.2.3)(jimp@1.6.1)(sharp@0.34.5)
@@ -262,8 +262,8 @@ importers:
specifier: ^2.1.3
version: 2.1.3
openclaw:
specifier: 2026.7.1
version: 2026.7.1(encoding@0.1.13)
specifier: 2026.7.1-2
version: 2026.7.1-2(encoding@0.1.13)
opusscript:
specifier: ^0.1.1
version: 0.1.1
@@ -1496,8 +1496,8 @@ packages:
resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==}
engines: {node: ^18.17.0 || >=20.5.0}
'@openclaw/ai@2026.7.1':
resolution: {integrity: sha512-FsKy5DXSHf4qyN8Huoz/10HZRgoEwLF4uk8UWaCafaIler+q5Fsl51HcrIqIrEe0S38OT7LOaxnR++MOshAlmw==}
'@openclaw/ai@2026.7.1-2':
resolution: {integrity: sha512-st+NH0cxlQqdbEur//yYqM7WlYBjeEBnop3cztJTSCONKjv6LNoGguI9cH65asZG94FdM/39z857isRGPnZvEw==}
engines: {node: '>=22.19.0'}
'@openclaw/discord@2026.7.1':
@@ -5315,8 +5315,8 @@ packages:
zod:
optional: true
openclaw@2026.7.1:
resolution: {integrity: sha512-ge/Xss99CHAjPL/ikmH/UFoiOrjcxDB4sW3y9mhyCD+dYW3wzV7TKbAVdkrXFgAG2d2BjpJofP97zUZ+umxo8g==}
openclaw@2026.7.1-2:
resolution: {integrity: sha512-ycF3yPcbjN6bUPeaUx6Mh6vze1hQWoD3CT/wWcmD7a8xaHHHRUaAlaq+lFxMHf1ssEgODVAwjlzYqp2twkYZ7g==}
engines: {node: '>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0'}
hasBin: true
@@ -6366,10 +6366,6 @@ packages:
resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==}
engines: {node: '>=18'}
tar@7.5.15:
resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==}
engines: {node: '>=18'}
tar@7.5.19:
resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==}
engines: {node: '>=18'}
@@ -7369,7 +7365,7 @@ snapshots:
ora: 5.4.1
read-binary-file-arch: 1.0.6
semver: 7.7.4
tar: 7.5.15
tar: 7.5.19
yargs: 17.7.2
transitivePeerDependencies:
- supports-color
@@ -8021,7 +8017,7 @@ snapshots:
'@kurkle/color@0.3.4': {}
'@larksuite/openclaw-lark@2026.7.9(openclaw@2026.7.1(encoding@0.1.13))':
'@larksuite/openclaw-lark@2026.7.9(openclaw@2026.7.1-2(encoding@0.1.13))':
dependencies:
'@larksuiteoapi/node-sdk': 1.66.1
'@sinclair/typebox': 0.34.49
@@ -8029,7 +8025,7 @@ snapshots:
undici-types: 8.3.0
zod: 4.4.3
optionalDependencies:
openclaw: 2026.7.1(encoding@0.1.13)
openclaw: 2026.7.1-2(encoding@0.1.13)
transitivePeerDependencies:
- bufferutil
- debug
@@ -8281,7 +8277,7 @@ snapshots:
dependencies:
semver: 7.7.4
'@openclaw/ai@2026.7.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)':
'@openclaw/ai@2026.7.1-2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)':
dependencies:
'@anthropic-ai/sdk': 0.109.1(zod@4.4.3)
'@google/genai': 2.10.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))
@@ -8301,9 +8297,9 @@ snapshots:
- ws
- zod
'@openclaw/discord@2026.7.1(openclaw@2026.7.1(encoding@0.1.13))':
'@openclaw/discord@2026.7.1(openclaw@2026.7.1-2(encoding@0.1.13))':
optionalDependencies:
openclaw: 2026.7.1(encoding@0.1.13)
openclaw: 2026.7.1-2(encoding@0.1.13)
'@openclaw/fs-safe@0.4.1':
optionalDependencies:
@@ -8314,13 +8310,13 @@ snapshots:
dependencies:
undici: 8.5.0
'@openclaw/qqbot@2026.7.1(openclaw@2026.7.1(encoding@0.1.13))':
'@openclaw/qqbot@2026.7.1(openclaw@2026.7.1-2(encoding@0.1.13))':
optionalDependencies:
openclaw: 2026.7.1(encoding@0.1.13)
openclaw: 2026.7.1-2(encoding@0.1.13)
'@openclaw/whatsapp@2026.7.1(openclaw@2026.7.1(encoding@0.1.13))':
'@openclaw/whatsapp@2026.7.1(openclaw@2026.7.1-2(encoding@0.1.13))':
optionalDependencies:
openclaw: 2026.7.1(encoding@0.1.13)
openclaw: 2026.7.1-2(encoding@0.1.13)
'@opentelemetry/semantic-conventions@1.43.0': {}
@@ -9004,7 +9000,7 @@ snapshots:
- '@emnapi/core'
- '@emnapi/runtime'
'@soimy/dingtalk@3.6.6(openclaw@2026.7.1(encoding@0.1.13))':
'@soimy/dingtalk@3.6.6(openclaw@2026.7.1-2(encoding@0.1.13))':
dependencies:
axios: 1.13.6(debug@4.4.3)
dingtalk-stream: 2.1.5
@@ -9013,7 +9009,7 @@ snapshots:
pdf-parse: 2.4.5
zod: 4.4.3
optionalDependencies:
openclaw: 2026.7.1(encoding@0.1.13)
openclaw: 2026.7.1-2(encoding@0.1.13)
transitivePeerDependencies:
- bufferutil
- debug
@@ -9059,9 +9055,9 @@ snapshots:
dependencies:
qrcode-terminal: 0.12.0
'@tencent-weixin/openclaw-weixin@2.4.6(openclaw@2026.7.1(encoding@0.1.13))':
'@tencent-weixin/openclaw-weixin@2.4.6(openclaw@2026.7.1-2(encoding@0.1.13))':
dependencies:
openclaw: 2026.7.1(encoding@0.1.13)
openclaw: 2026.7.1-2(encoding@0.1.13)
qrcode-terminal: 0.12.0
zod: 4.4.3
@@ -9550,7 +9546,7 @@ snapshots:
- debug
- utf-8-validate
'@wecom/wecom-openclaw-plugin@2026.7.2(openclaw@2026.7.1(encoding@0.1.13))':
'@wecom/wecom-openclaw-plugin@2026.7.2(openclaw@2026.7.1-2(encoding@0.1.13))':
dependencies:
'@wecom/aibot-node-sdk': 1.0.6
fast-xml-parser: 5.7.3
@@ -9558,7 +9554,7 @@ snapshots:
undici: 7.24.6
zod: 4.4.3
optionalDependencies:
openclaw: 2026.7.1(encoding@0.1.13)
openclaw: 2026.7.1-2(encoding@0.1.13)
transitivePeerDependencies:
- bufferutil
- debug
@@ -9980,7 +9976,7 @@ snapshots:
minipass-pipeline: 1.2.4
p-map: 7.0.4
ssri: 12.0.0
tar: 7.5.15
tar: 7.5.19
unique-filename: 4.0.0
cacheable-lookup@5.0.4: {}
@@ -12625,7 +12621,7 @@ snapshots:
nopt: 8.1.0
proc-log: 5.0.0
semver: 7.7.4
tar: 7.5.15
tar: 7.5.19
tinyglobby: 0.2.15
which: 5.0.0
transitivePeerDependencies:
@@ -12699,7 +12695,7 @@ snapshots:
ws: 8.21.0
zod: 4.4.3
openclaw@2026.7.1(encoding@0.1.13):
openclaw@2026.7.1-2(encoding@0.1.13):
dependencies:
'@agentclientprotocol/sdk': 1.1.0(zod@4.4.3)
'@anthropic-ai/sdk': 0.109.1(zod@4.4.3)
@@ -12714,7 +12710,7 @@ snapshots:
'@mistralai/mistralai': 2.4.0
'@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3)
'@mozilla/readability': 0.6.0
'@openclaw/ai': 2026.7.1(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
'@openclaw/ai': 2026.7.1-2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
'@openclaw/fs-safe': 0.4.1
'@openclaw/proxyline': 0.3.3(undici@8.5.0)
'@silvia-odwyer/photon-node': 0.3.4
@@ -13971,14 +13967,6 @@ snapshots:
minizlib: 3.1.0
yallist: 5.0.0
tar@7.5.15:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
minipass: 7.1.3
minizlib: 3.1.0
yallist: 5.0.0
tar@7.5.19:
dependencies:
'@isaacs/fs-minipass': 4.0.1
@@ -2,6 +2,10 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const { removeUpgradeSnapshotMock } = vi.hoisted(() => ({
removeUpgradeSnapshotMock: vi.fn(async () => ({ status: 'removed' as const, snapshotDir: '/tmp/snapshot' })),
}));
vi.mock('electron', () => ({
app: {
getPath: () => '/tmp',
@@ -23,6 +27,10 @@ vi.mock('@electron/utils/config', () => ({
PORTS: { OPENCLAW_GATEWAY: 18789 },
}));
vi.mock('@electron/utils/openclaw-upgrade-snapshot', () => ({
removeOpenClaw2026_7_1UpgradeSnapshot: removeUpgradeSnapshotMock,
}));
vi.mock('@electron/gateway/startup-orchestrator', () => ({
runGatewayStartupSequence: vi.fn(async () => {
throw new Error('startup unavailable in unit test');
@@ -103,6 +111,7 @@ describe('GatewayManager gatewayReady fallback', () => {
const readyUpdate = statusUpdates.find((u) => u.gatewayReady === true);
expect(readyUpdate).toBeDefined();
expect(rpcSpy).toHaveBeenCalledWith('system-presence', {}, 5_000);
expect(removeUpgradeSnapshotMock).toHaveBeenCalledOnce();
});
it('keeps gatewayReady=false when fallback RPC router probe fails', async () => {
+4 -3
View File
@@ -8,14 +8,14 @@ import { describe, expect, it } from 'vitest';
const require = createRequire(import.meta.url);
describe('openclaw bundle config', () => {
it('pins the OpenClaw 2026.7.1 runtime compatibility matrix', () => {
it('pins the OpenClaw 2026.7.1-2 runtime compatibility matrix', () => {
const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};
expect(packageJson.dependencies?.['@agentclientprotocol/sdk']).toBe('1.1.0');
expect(packageJson.devDependencies).toMatchObject({
openclaw: '2026.7.1',
openclaw: '2026.7.1-2',
electron: '40.10.6',
'@openclaw/discord': '2026.7.1',
'@openclaw/qqbot': '2026.7.1',
@@ -66,7 +66,8 @@ describe('openclaw bundle config', () => {
expect(lockfile).not.toContain("'@soimy/dingtalk@3.6.4':");
expect(lockfile).not.toContain("'@wecom/wecom-openclaw-plugin@2026.6.23':");
expect(lockfile).not.toContain("'@larksuite/openclaw-lark@2026.6.10':");
expect(lockfile).toContain("'@openclaw/ai@2026.7.1':");
expect(lockfile).not.toContain("'@openclaw/ai@2026.7.1':");
expect(lockfile).toContain("'@openclaw/ai@2026.7.1-2':");
});
it('includes Electron runtime-only packages needed in packaged builds', async () => {
@@ -2,9 +2,11 @@
import { chmod, mkdtemp, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { afterEach, describe, expect, it } from 'vitest';
import {
ensureOpenClaw2026_7_1UpgradeSnapshot,
quarantineLegacyUpdateCheckState,
removeOpenClaw2026_7_1UpgradeSnapshot,
} from '@electron/utils/openclaw-upgrade-snapshot';
@@ -64,6 +66,57 @@ describe('OpenClaw 2026.7.1 upgrade snapshot', () => {
.resolves.toBe('{"version":"old"}\n');
});
it('quarantines legacy update-check JSON when SQLite is already authoritative', async () => {
const stateDir = await createTempStateDir();
const sourcePath = join(stateDir, 'update-check.json');
const sqliteDir = join(stateDir, 'state');
const sqlitePath = join(sqliteDir, 'openclaw.sqlite');
await mkdir(sqliteDir, { recursive: true });
await writeFile(sourcePath, '{"lastCheckedAt":"legacy"}\n');
const db = new DatabaseSync(sqlitePath);
db.exec(`
CREATE TABLE update_check_state (
state_key TEXT PRIMARY KEY,
last_checked_at TEXT
);
INSERT INTO update_check_state (state_key, last_checked_at)
VALUES ('default', 'canonical');
`);
db.close();
const result = await quarantineLegacyUpdateCheckState({ stateDir });
expect(result.status).toBe('quarantined');
expect(result.backupPath).toContain('clawx-openclaw-2026.7.1-legacy-update-check.json');
await expect(stat(sourcePath)).rejects.toThrow();
await expect(readFile(result.backupPath!, 'utf8')).resolves.toBe('{"lastCheckedAt":"legacy"}\n');
expect((await stat(result.backupPath!)).mode & 0o777).toBe(0o600);
});
it('leaves legacy update-check JSON for upstream import when SQLite has no canonical row', async () => {
const stateDir = await createTempStateDir();
const sourcePath = join(stateDir, 'update-check.json');
const sqliteDir = join(stateDir, 'state');
const sqlitePath = join(sqliteDir, 'openclaw.sqlite');
await mkdir(sqliteDir, { recursive: true });
await writeFile(sourcePath, '{"lastCheckedAt":"legacy"}\n');
const db = new DatabaseSync(sqlitePath);
db.exec(`
CREATE TABLE update_check_state (
state_key TEXT PRIMARY KEY,
last_checked_at TEXT
);
`);
db.close();
await expect(quarantineLegacyUpdateCheckState({ stateDir })).resolves.toMatchObject({
status: 'deferred',
sourcePath,
});
await expect(readFile(sourcePath, 'utf8')).resolves.toBe('{"lastCheckedAt":"legacy"}\n');
});
it('removes the snapshot directory after successful cleanup', async () => {
const stateDir = await createTempStateDir();
const configPath = join(stateDir, 'openclaw.json');