mirror of
https://github.com/ValueCell-ai/ClawX.git
synced 2026-08-14 08:53:09 +00:00
feat:upgrade openclaw to 7.1 (#1193)
This commit is contained in:
@@ -33,6 +33,41 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# The runtime compatibility test executes Electron to assert its embedded
|
||||
# Node and SQLite versions, so this job cannot rely on the package alone.
|
||||
# Use the same extraction path as Electron E2E because install.js can
|
||||
# leave a partially extracted dist directory on GitHub-hosted runners.
|
||||
- name: Install Electron binary for runtime compatibility test
|
||||
shell: bash
|
||||
env:
|
||||
force_no_cache: 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
unset ELECTRON_SKIP_BINARY_DOWNLOAD
|
||||
ELECTRON_DIR="$(node -p "require('path').dirname(require.resolve('electron/package.json'))")"
|
||||
echo "Electron package dir: $ELECTRON_DIR"
|
||||
rm -rf "$ELECTRON_DIR/dist" "$ELECTRON_DIR/path.txt"
|
||||
mkdir -p "$ELECTRON_DIR/dist"
|
||||
|
||||
ZIP="$(cd "$ELECTRON_DIR" && node -e "
|
||||
const { downloadArtifact } = require('@electron/get');
|
||||
const { version } = require('./package.json');
|
||||
downloadArtifact({ version, artifactName: 'electron', force: true })
|
||||
.then((z) => { process.stdout.write(z); process.exit(0); })
|
||||
.catch((e) => { console.error(e); process.exit(1); });
|
||||
")"
|
||||
ZIP_SIZE="$(stat -c%s "$ZIP")"
|
||||
echo "Downloaded zip: $ZIP ($ZIP_SIZE bytes)"
|
||||
|
||||
unzip -oq "$ZIP" -d "$ELECTRON_DIR/dist"
|
||||
echo "Extracted top-level entries: $(ls -1 "$ELECTRON_DIR/dist" | wc -l | tr -d ' ')"
|
||||
if [ -f "$ELECTRON_DIR/dist/electron.d.ts" ]; then
|
||||
mv "$ELECTRON_DIR/dist/electron.d.ts" "$ELECTRON_DIR/electron.d.ts"
|
||||
fi
|
||||
test -f "$ELECTRON_DIR/dist/electron"
|
||||
chmod +x "$ELECTRON_DIR/dist/electron"
|
||||
printf '%s' 'electron' > "$ELECTRON_DIR/path.txt"
|
||||
|
||||
- name: Generate extension bridge
|
||||
run: pnpm run ext:bridge
|
||||
|
||||
|
||||
+1
-1
@@ -337,7 +337,7 @@ AI を開発ワークフローに統合できます。エージェントを使
|
||||
|
||||
### 前提条件
|
||||
|
||||
- **Node.js**: 22.19以上(LTS推奨)
|
||||
- **Node.js**: 対応するメジャー系列の 22.22.3以上、24.15.0以上、または25.9.0以上(Node 24 LTS推奨)
|
||||
- **パッケージマネージャー**: pnpm 9以上(推奨)またはnpm
|
||||
- **Linux(Ubuntu/Debian)**: Electron を実行する前に、必要なシステムライブラリをインストールしてください:
|
||||
```bash
|
||||
|
||||
@@ -340,7 +340,7 @@ Chain multiple skills together to create sophisticated automation pipelines. Pro
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js**: 22.19+ (LTS recommended)
|
||||
- **Node.js**: 22.22.3+, 24.15.0+, or 25.9.0+ within the corresponding supported major line (Node 24 LTS recommended)
|
||||
- **Package Manager**: pnpm 9+ (recommended) or npm
|
||||
- **Linux (Ubuntu/Debian)**: Install required system libraries before running Electron:
|
||||
```bash
|
||||
|
||||
+1
-1
@@ -306,7 +306,7 @@ ClawX использует **двухпроцессную архитектуру
|
||||
|
||||
### Требования
|
||||
|
||||
- **Node.js**: 22.19+ (рекомендуется LTS)
|
||||
- **Node.js**: 22.22.3+, 24.15.0+ или 25.9.0+ в пределах соответствующей основной версии (рекомендуется Node 24 LTS)
|
||||
- **Менеджер пакетов**: pnpm 9+ (рекомендуется) или npm
|
||||
|
||||
### Структура проекта
|
||||
|
||||
+1
-1
@@ -341,7 +341,7 @@ ACP Chat 也可在 runtime 以可信结构化媒体投递图像生成结果时
|
||||
|
||||
### 前置要求
|
||||
|
||||
- **Node.js**:22.19+(推荐 LTS 版本)
|
||||
- **Node.js**:对应主版本范围内的 22.22.3+、24.15.0+ 或 25.9.0+(推荐 Node 24 LTS)
|
||||
- **包管理器**:pnpm 9+(推荐)或 npm
|
||||
- **Linux(Ubuntu/Debian)**:运行 Electron 前,请先安装所需系统库:
|
||||
```bash
|
||||
|
||||
@@ -33,8 +33,9 @@ import { buildProxyEnv, resolveProxySettings } from '../utils/proxy';
|
||||
import { syncProxyConfigToOpenClaw } from '../utils/openclaw-proxy';
|
||||
import { logger } from '../utils/logger';
|
||||
import { prependPathEntry } from '../utils/env-path';
|
||||
import { copyPluginFromNodeModules, fixupPluginManifest, cpSyncSafe, buildCandidateSources, repairTrustedOfficialPluginInstallRecords, syncTrustedOfficialPluginInstallRecord, resolvePluginNpmPackagePath } from '../utils/plugin-install';
|
||||
import { copyPluginFromNodeModules, fixupPluginManifest, cpSyncSafe, buildCandidateSources, repairTrustedOfficialPluginInstallRecords, removeTrustedOfficialPluginInstallRecord, syncTrustedOfficialPluginInstallRecord, resolvePluginNpmPackagePath } from '../utils/plugin-install';
|
||||
import { CLAWX_OPENAI_IMAGE_PROVIDER_KEY } from '../utils/openclaw-image-relay-constants';
|
||||
import { ensureOpenClaw2026_7_1UpgradeSnapshot } from '../utils/openclaw-upgrade-snapshot';
|
||||
import { stripSystemdSupervisorEnv } from './config-sync-env';
|
||||
import { cleanupAgentsSymlinkedSkills, cleanupStalePluginRuntimeDeps } from './skills-symlink-cleanup';
|
||||
import {
|
||||
@@ -266,6 +267,18 @@ function cleanupUnconfiguredChannelPlugins(configuredChannels: string[]): boolea
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
function cleanupUnconfiguredChannelPluginInstallRecords(configuredChannels: string[]): void {
|
||||
const configuredSet = new Set(configuredChannels);
|
||||
for (const [channelType, { dirName }] of Object.entries(CHANNEL_PLUGIN_MAP)) {
|
||||
if (configuredSet.has(channelType)) continue;
|
||||
// Metadata can outlive the directory (for example after an interrupted
|
||||
// 2026.6.10 → 2026.7.1 migration). OpenClaw validates tracked records even
|
||||
// when the channel is no longer configured, so reconcile this on every
|
||||
// launch rather than hiding it behind the directory-maintenance cache.
|
||||
removeTrustedOfficialPluginInstallRecord(dirName);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveImageGenerationPrimary(config: unknown): string | null {
|
||||
if (!config || typeof config !== 'object') return null;
|
||||
const agents = (config as { agents?: unknown }).agents;
|
||||
@@ -526,7 +539,10 @@ export async function syncGatewayConfigBeforeLaunch(
|
||||
// Always refresh trusted install metadata through ClawX — this must not
|
||||
// be skipped when plugin-maintenance is cache-hit, otherwise official
|
||||
// external plugins like WhatsApp fail openKeyedStore at runtime.
|
||||
measureSync(timingsMs, 'trustedPluginInstallSyncMs', repairTrustedOfficialPluginInstallRecords);
|
||||
measureSync(timingsMs, 'trustedPluginInstallSyncMs', () => {
|
||||
cleanupUnconfiguredChannelPluginInstallRecords(configuredChannels);
|
||||
repairTrustedOfficialPluginInstallRecords();
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn('Failed to auto-upgrade plugins:', err);
|
||||
}
|
||||
@@ -625,6 +641,19 @@ export async function prepareGatewayLaunchContext(port: number): Promise<Gateway
|
||||
throw new Error(`OpenClaw package not found at: ${openclawDir}`);
|
||||
}
|
||||
|
||||
await measureAsync(timingsMs, 'upgradeSnapshotMs', async () => {
|
||||
try {
|
||||
const snapshot = await ensureOpenClaw2026_7_1UpgradeSnapshot();
|
||||
if (snapshot.status === 'created') {
|
||||
logger.info(`[upgrade] Created OpenClaw 2026.7.1 pre-migration snapshot (${snapshot.files.length} files): ${snapshot.snapshotDir}`);
|
||||
}
|
||||
} catch (error) {
|
||||
// OpenClaw also maintains migration-specific backups. Keep startup
|
||||
// available if the additional ClawX safety snapshot cannot be written.
|
||||
logger.warn('[upgrade] Failed to create OpenClaw 2026.7.1 pre-migration snapshot:', error);
|
||||
}
|
||||
});
|
||||
|
||||
const appSettings = await measureAsync(timingsMs, 'settingsMs', getAllSettings);
|
||||
const prelaunchSummary = await measureAsync(timingsMs, 'prelaunchSyncMs', async () => (
|
||||
await syncGatewayConfigBeforeLaunch(appSettings, openclawDir)
|
||||
|
||||
+34
-11
@@ -24,6 +24,7 @@ import {
|
||||
type GatewayLifecycleState,
|
||||
getReconnectScheduleDecision,
|
||||
getReconnectSkipReason,
|
||||
isOpenClawFatalConfigExitCode,
|
||||
} from './process-policy';
|
||||
import {
|
||||
clearPendingGatewayRequests,
|
||||
@@ -61,6 +62,11 @@ import {
|
||||
recordGatewayStartupStderrLine,
|
||||
} from './startup-stderr';
|
||||
import { runGatewayStartupSequence } from './startup-orchestrator';
|
||||
import {
|
||||
hasFatalRuntimeFailureSignal,
|
||||
hasInvalidConfigFailureSignal,
|
||||
hasStartupMigrationLockSignal,
|
||||
} from './startup-recovery';
|
||||
import {
|
||||
GatewayCapabilityMonitor,
|
||||
type GatewayCapabilityName,
|
||||
@@ -462,7 +468,17 @@ export class GatewayManager extends EventEmitter {
|
||||
error
|
||||
);
|
||||
this.setStatus({ state: 'error', error: String(error) });
|
||||
if (this.shouldReconnect) {
|
||||
const fatalStartupFailure = isOpenClawFatalConfigExitCode(this.processExitCode)
|
||||
|| hasFatalRuntimeFailureSignal(error, this.recentStartupStderrLines)
|
||||
|| hasStartupMigrationLockSignal(error, this.recentStartupStderrLines)
|
||||
|| hasInvalidConfigFailureSignal(error, this.recentStartupStderrLines);
|
||||
if (fatalStartupFailure) {
|
||||
// OpenClaw 2026.7.1 uses EX_CONFIG for fatal configuration failures.
|
||||
// Runtime and SQLite compatibility failures are likewise not repaired
|
||||
// by restarting the same binary, so leave recovery to a manual start.
|
||||
this.shouldReconnect = false;
|
||||
logger.error('Gateway startup failed fatally; automatic reconnect disabled');
|
||||
} else if (this.shouldReconnect) {
|
||||
logger.warn('Gateway start failed; scheduling auto-reconnect recovery');
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
@@ -1121,16 +1137,23 @@ export class GatewayManager extends EventEmitter {
|
||||
this.setStatus({ state: 'stopped' });
|
||||
}
|
||||
|
||||
// Always attempt reconnect from process exit. scheduleReconnect()
|
||||
// internally checks shouldReconnect and reconnect-timer guards, so
|
||||
// calling it unconditionally is safe — intentional stop() calls set
|
||||
// shouldReconnect=false which makes scheduleReconnect() no-op.
|
||||
//
|
||||
// On Windows, the WS close handler intentionally skips reconnect
|
||||
// (to avoid racing with this exit handler). However, WS close
|
||||
// fires *before* process exit and sets state='stopped', which
|
||||
// previously caused this handler to also skip reconnect — leaving
|
||||
// the gateway permanently dead with no recovery path.
|
||||
const orchestratedStartupFailure = isOpenClawFatalConfigExitCode(code)
|
||||
|| hasFatalRuntimeFailureSignal(undefined, this.recentStartupStderrLines)
|
||||
|| hasStartupMigrationLockSignal(undefined, this.recentStartupStderrLines)
|
||||
|| hasInvalidConfigFailureSignal(undefined, this.recentStartupStderrLines);
|
||||
if (orchestratedStartupFailure) {
|
||||
// During startup the orchestrator may still perform its one bounded
|
||||
// doctor repair. Do not race it with an independent reconnect timer.
|
||||
// If orchestration cannot recover, start() disables reconnect in its
|
||||
// catch path so migration/config failures cannot create an outer loop.
|
||||
if (this.status.state !== 'starting') this.shouldReconnect = false;
|
||||
logger.error(`Gateway process reported a non-retriable startup condition (code=${String(code)}); reconnect not scheduled`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Always attempt reconnect from non-fatal process exits.
|
||||
// scheduleReconnect() internally checks shouldReconnect and timer
|
||||
// guards, so intentional stop() remains a no-op.
|
||||
this.scheduleReconnect();
|
||||
},
|
||||
onError: () => {
|
||||
|
||||
@@ -10,6 +10,13 @@ export const DEFAULT_RECONNECT_CONFIG: ReconnectConfig = {
|
||||
maxDelay: 30000,
|
||||
};
|
||||
|
||||
/** sysexits(3) EX_CONFIG, used by OpenClaw 2026.7.1 for fatal config startup errors. */
|
||||
export const OPENCLAW_EX_CONFIG_EXIT_CODE = 78;
|
||||
|
||||
export function isOpenClawFatalConfigExitCode(code: number | null | undefined): boolean {
|
||||
return code === OPENCLAW_EX_CONFIG_EXIT_CODE;
|
||||
}
|
||||
|
||||
export function nextLifecycleEpoch(currentEpoch: number): number {
|
||||
return currentEpoch + 1;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,25 @@
|
||||
const INVALID_CONFIG_PATTERNS: RegExp[] = [
|
||||
/\binvalid config\b/i,
|
||||
/\bconfig invalid\b/i,
|
||||
/\bfatal configuration error\b/i,
|
||||
/\bunrecognized key\b/i,
|
||||
/\bstartup migration(?:s)?\b.*\b(?:blocked|failed|did not complete cleanly)\b/i,
|
||||
/\bmigration\b.*\bopenclaw doctor --fix\b/i,
|
||||
/\brun:\s*openclaw doctor --fix\b/i,
|
||||
];
|
||||
|
||||
const FATAL_RUNTIME_PATTERNS: RegExp[] = [
|
||||
/\bNode(?:\.js)?\b.*\boutside the supported range\b/i,
|
||||
/\buses SQLite\b.*\bnot WAL-reset-safe\b/i,
|
||||
/\bSQLite\b.*\bWAL-reset-safe runtime required\b/i,
|
||||
/\bInstall Node 24\.15\+.*\bNode 22\.22\.3\+\b/i,
|
||||
];
|
||||
|
||||
const STARTUP_MIGRATION_LOCK_PATTERNS: RegExp[] = [
|
||||
/\bstartup migrations? (?:is|are) already running\b/i,
|
||||
/\bretry after the other gateway finishes\b/i,
|
||||
];
|
||||
|
||||
const TRANSIENT_START_ERROR_PATTERNS: RegExp[] = [
|
||||
/WebSocket closed before handshake/i,
|
||||
/ECONNREFUSED/i,
|
||||
@@ -61,6 +76,33 @@ export function hasInvalidConfigFailureSignal(
|
||||
return isInvalidConfigSignal(errorText);
|
||||
}
|
||||
|
||||
function startupFailureCandidates(startupError: unknown, startupStderrLines: string[]): string[] {
|
||||
return [
|
||||
...startupStderrLines,
|
||||
startupError instanceof Error
|
||||
? `${startupError.name}: ${startupError.message}`
|
||||
: String(startupError ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
/** Returns true for OpenClaw runtime/SQLite failures that doctor cannot repair. */
|
||||
export function hasFatalRuntimeFailureSignal(
|
||||
startupError: unknown,
|
||||
startupStderrLines: string[],
|
||||
): boolean {
|
||||
return startupFailureCandidates(startupError, startupStderrLines)
|
||||
.some((text) => FATAL_RUNTIME_PATTERNS.some((pattern) => pattern.test(text)));
|
||||
}
|
||||
|
||||
/** Returns true while another/stale OpenClaw startup migration lease is active. */
|
||||
export function hasStartupMigrationLockSignal(
|
||||
startupError: unknown,
|
||||
startupStderrLines: string[],
|
||||
): boolean {
|
||||
return startupFailureCandidates(startupError, startupStderrLines)
|
||||
.some((text) => STARTUP_MIGRATION_LOCK_PATTERNS.some((pattern) => pattern.test(text)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry guard for one-time config repair during a single startup flow.
|
||||
*/
|
||||
@@ -136,12 +178,18 @@ export function getGatewayStartupRecoveryAction(options: {
|
||||
attempt: number;
|
||||
maxAttempts: number;
|
||||
}): GatewayStartupRecoveryAction {
|
||||
if (shouldAttemptConfigAutoRepair(
|
||||
options.startupError,
|
||||
options.startupStderrLines,
|
||||
options.configRepairAttempted,
|
||||
)) {
|
||||
return 'repair';
|
||||
if (
|
||||
hasFatalRuntimeFailureSignal(options.startupError, options.startupStderrLines)
|
||||
|| hasStartupMigrationLockSignal(options.startupError, options.startupStderrLines)
|
||||
) {
|
||||
return 'fail';
|
||||
}
|
||||
|
||||
if (hasInvalidConfigFailureSignal(options.startupError, options.startupStderrLines)) {
|
||||
// One doctor pass is the only automated repair. If the same migration or
|
||||
// config failure remains afterward, stop instead of treating the generic
|
||||
// process-exited error as transient.
|
||||
return options.configRepairAttempted ? 'fail' : 'repair';
|
||||
}
|
||||
|
||||
if (options.attempt < options.maxAttempts && isTransientGatewayStartError(options.startupError)) {
|
||||
|
||||
@@ -393,11 +393,13 @@ export class AcpChatService {
|
||||
this.historicalGeneration = null;
|
||||
}
|
||||
this.permissionsEnabled = true;
|
||||
const messageId = payload.messageId ?? randomUUID();
|
||||
await connection.prompt({
|
||||
sessionId: acpSessionId,
|
||||
prompt,
|
||||
messageId: payload.messageId ?? randomUUID(),
|
||||
_meta: { sessionKey: payload.sessionKey, prefixCwd: true },
|
||||
// ACP 1.1 removed messageId from the PromptRequest wire shape. Keep
|
||||
// ClawX correlation metadata in the protocol extension envelope.
|
||||
_meta: { sessionKey: payload.sessionKey, prefixCwd: true, messageId },
|
||||
});
|
||||
this.trace('session/prompt:success', {
|
||||
sessionKey: payload.sessionKey,
|
||||
|
||||
@@ -53,7 +53,7 @@ interface CronSessionKeyParts {
|
||||
|
||||
interface CronSessionFallbackMessage {
|
||||
id: string;
|
||||
role: 'assistant' | 'system';
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
timestamp: number;
|
||||
isError?: boolean;
|
||||
@@ -117,14 +117,14 @@ function buildCronRunMessage(entry: CronRunLogEntry, index: number): CronSession
|
||||
|
||||
return {
|
||||
id: `cron-run-${entry.sessionId ?? entry.ts ?? index}`,
|
||||
role: status === 'error' ? 'system' : 'assistant',
|
||||
role: 'assistant',
|
||||
content,
|
||||
timestamp,
|
||||
...(status === 'error' ? { isError: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function readCronRunLog(jobId: string): Promise<CronRunLogEntry[]> {
|
||||
async function readLegacyCronRunLog(jobId: string): Promise<CronRunLogEntry[]> {
|
||||
const logPath = join(getOpenClawConfigDir(), 'cron', 'runs', `${jobId}.jsonl`);
|
||||
const raw = await readFile(logPath, 'utf8').catch(() => '');
|
||||
if (!raw.trim()) return [];
|
||||
@@ -145,6 +145,24 @@ async function readCronRunLog(jobId: string): Promise<CronRunLogEntry[]> {
|
||||
return entries;
|
||||
}
|
||||
|
||||
async function readCronRunHistory(
|
||||
gatewayManager: GatewayManager,
|
||||
jobId: string,
|
||||
limit: number,
|
||||
): Promise<CronRunLogEntry[]> {
|
||||
try {
|
||||
const result = await gatewayManager.rpc<{ entries?: CronRunLogEntry[] }>('cron.runs', {
|
||||
id: jobId,
|
||||
limit,
|
||||
sortDir: 'asc',
|
||||
}, 8000);
|
||||
if (Array.isArray(result?.entries)) return result.entries;
|
||||
} catch {
|
||||
// OpenClaw versions before SQLite cron history may not expose cron.runs.
|
||||
}
|
||||
return readLegacyCronRunLog(jobId);
|
||||
}
|
||||
|
||||
async function readSessionStoreEntry(
|
||||
agentId: string,
|
||||
sessionKey: string,
|
||||
@@ -204,12 +222,10 @@ function buildCronSessionFallbackMessages(params: {
|
||||
: (normalizeTimestampMs(params.job?.state?.runningAtMs) ?? params.sessionEntry?.updatedAt);
|
||||
|
||||
if (taskName || prompt) {
|
||||
const lines = [taskName ? `Scheduled task: ${taskName}` : 'Scheduled task'];
|
||||
if (prompt) lines.push(`Prompt: ${prompt}`);
|
||||
messages.push({
|
||||
id: `cron-meta-${parsed.jobId}`,
|
||||
role: 'system',
|
||||
content: lines.join('\n'),
|
||||
role: 'user',
|
||||
content: prompt || taskName,
|
||||
timestamp: Math.max(0, (firstRelevantTimestamp ?? Date.now()) - 1),
|
||||
});
|
||||
}
|
||||
@@ -224,17 +240,10 @@ function buildCronSessionFallbackMessages(params: {
|
||||
if (runningAt) {
|
||||
messages.push({
|
||||
id: `cron-running-${parsed.jobId}`,
|
||||
role: 'system',
|
||||
role: 'assistant',
|
||||
content: 'This scheduled task is still running in OpenClaw, but no chat transcript is available yet.',
|
||||
timestamp: runningAt,
|
||||
});
|
||||
} else if (messages.length === 0) {
|
||||
messages.push({
|
||||
id: `cron-empty-${parsed.jobId}`,
|
||||
role: 'system',
|
||||
content: 'No chat transcript is available for this scheduled task yet.',
|
||||
timestamp: params.sessionEntry?.updatedAt ?? Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +578,7 @@ export function createCronApi({ gatewayManager }: { gatewayManager: GatewayManag
|
||||
const [jobsResult, runs, sessionEntry] = await Promise.all([
|
||||
gatewayManager.rpc('cron.list', { includeDisabled: true }, 8000)
|
||||
.catch(() => ({ jobs: [] as GatewayCronJob[] })),
|
||||
readCronRunLog(parsedSession.jobId),
|
||||
readCronRunHistory(gatewayManager, parsedSession.jobId, limit),
|
||||
readSessionStoreEntry(parsedSession.agentId, sessionKey),
|
||||
]);
|
||||
const jobs = (jobsResult as { jobs?: GatewayCronJob[] }).jobs ?? [];
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { copyFile, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
||||
import { basename, dirname, join, relative, resolve } from 'node:path';
|
||||
import { resolveOpenClawConfigPath, resolveOpenClawStateDir } from './paths';
|
||||
|
||||
const UPGRADE_ID = 'openclaw-2026.7.1';
|
||||
const AGENT_AUTH_BASENAMES = new Set([
|
||||
'auth-profiles.json',
|
||||
'openclaw-agent.sqlite',
|
||||
'openclaw-agent.sqlite-wal',
|
||||
'openclaw-agent.sqlite-shm',
|
||||
]);
|
||||
|
||||
export type OpenClawUpgradeSnapshotResult = {
|
||||
status: 'created' | 'exists';
|
||||
snapshotDir: string;
|
||||
files: string[];
|
||||
};
|
||||
|
||||
type SnapshotOptions = {
|
||||
stateDir?: string;
|
||||
configPath?: string;
|
||||
};
|
||||
|
||||
async function isRegularFile(path: string): Promise<boolean> {
|
||||
try {
|
||||
return (await stat(path)).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyFileIfPresent(source: string, destination: string, copied: string[]): Promise<void> {
|
||||
if (!await isRegularFile(source)) return;
|
||||
await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
|
||||
await copyFile(source, destination);
|
||||
copied.push(destination);
|
||||
}
|
||||
|
||||
async function copyTree(
|
||||
sourceRoot: string,
|
||||
destinationRoot: string,
|
||||
copied: string[],
|
||||
includeFile: (name: string) => boolean,
|
||||
): Promise<void> {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir(sourceRoot, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const source = join(sourceRoot, entry.name);
|
||||
const destination = join(destinationRoot, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await copyTree(source, destination, copied, includeFile);
|
||||
} else if (entry.isFile() && includeFile(entry.name)) {
|
||||
await copyFileIfPresent(source, destination, copied);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a one-time pre-migration snapshot before ClawX first starts the
|
||||
* OpenClaw 2026.7.1 Gateway. SQLite databases are copied together with their
|
||||
* WAL/SHM sidecars; OpenClaw remains responsible for applying migrations.
|
||||
*/
|
||||
export async function ensureOpenClaw2026_7_1UpgradeSnapshot(
|
||||
options: SnapshotOptions = {},
|
||||
): Promise<OpenClawUpgradeSnapshotResult> {
|
||||
const stateDir = resolve(options.stateDir ?? resolveOpenClawStateDir());
|
||||
const configPath = resolve(options.configPath ?? resolveOpenClawConfigPath());
|
||||
const snapshotDir = join(stateDir, 'backups', `clawx-${UPGRADE_ID}-pre-migration`);
|
||||
const markerPath = join(snapshotDir, 'snapshot.json');
|
||||
|
||||
if (await isRegularFile(markerPath)) {
|
||||
try {
|
||||
const marker = JSON.parse(await readFile(markerPath, 'utf8')) as { files?: unknown };
|
||||
return {
|
||||
status: 'exists',
|
||||
snapshotDir,
|
||||
files: Array.isArray(marker.files)
|
||||
? marker.files.filter((value): value is string => typeof value === 'string')
|
||||
: [],
|
||||
};
|
||||
} catch {
|
||||
// Replace malformed/incomplete snapshots below.
|
||||
}
|
||||
}
|
||||
|
||||
const tempDir = `${snapshotDir}.tmp-${process.pid}-${Date.now()}`;
|
||||
const copiedDestinations: string[] = [];
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
await mkdir(tempDir, { recursive: true, mode: 0o700 });
|
||||
|
||||
try {
|
||||
await copyFileIfPresent(configPath, join(tempDir, 'config', basename(configPath)), copiedDestinations);
|
||||
|
||||
for (const databasePath of [
|
||||
join(stateDir, 'openclaw.sqlite'),
|
||||
join(stateDir, 'state', 'openclaw.sqlite'),
|
||||
]) {
|
||||
const relativeDatabase = relative(stateDir, databasePath);
|
||||
for (const suffix of ['', '-wal', '-shm']) {
|
||||
await copyFileIfPresent(
|
||||
`${databasePath}${suffix}`,
|
||||
join(tempDir, 'state-files', `${relativeDatabase}${suffix}`),
|
||||
copiedDestinations,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await copyTree(
|
||||
join(stateDir, 'agents'),
|
||||
join(tempDir, 'agents'),
|
||||
copiedDestinations,
|
||||
(name) => AGENT_AUTH_BASENAMES.has(name),
|
||||
);
|
||||
await copyTree(
|
||||
join(stateDir, 'credentials'),
|
||||
join(tempDir, 'credentials'),
|
||||
copiedDestinations,
|
||||
() => true,
|
||||
);
|
||||
|
||||
const files = copiedDestinations.map((path) => relative(tempDir, path)).sort();
|
||||
await writeFile(join(tempDir, 'snapshot.json'), `${JSON.stringify({
|
||||
upgrade: UPGRADE_ID,
|
||||
createdAt: new Date().toISOString(),
|
||||
configPath,
|
||||
stateDir,
|
||||
files,
|
||||
}, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
||||
|
||||
await rm(snapshotDir, { recursive: true, force: true });
|
||||
await mkdir(dirname(snapshotDir), { recursive: true, mode: 0o700 });
|
||||
await rename(tempDir, snapshotDir);
|
||||
return { status: 'created', snapshotDir, files };
|
||||
} catch (error) {
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,10 @@ function resolveOpenClawStateDir(): string {
|
||||
}
|
||||
|
||||
function resolveOpenClawStateSqlitePath(): string {
|
||||
return join(resolveOpenClawStateDir(), 'openclaw.sqlite');
|
||||
// OpenClaw 2026.7.1 moved the shared state database under state/.
|
||||
// Writing the legacy root-level database leaves Gateway migrations reading
|
||||
// stale plugin records from the canonical database.
|
||||
return join(resolveOpenClawStateDir(), 'state', 'openclaw.sqlite');
|
||||
}
|
||||
|
||||
function parseInstallRecordsJson(raw: unknown): Record<string, Record<string, unknown>> {
|
||||
@@ -88,6 +91,7 @@ export function upsertPluginInstallRecordsIntoSqlite(
|
||||
|
||||
ensureOpenClawStateDirExists();
|
||||
const sqlitePath = resolveOpenClawStateSqlitePath();
|
||||
mkdirSync(join(resolveOpenClawStateDir(), 'state'), { recursive: true });
|
||||
|
||||
let db: DatabaseSync | null = null;
|
||||
try {
|
||||
@@ -157,6 +161,66 @@ export function upsertPluginInstallRecordsIntoSqlite(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove install records that must remain ClawX-managed rather than updated
|
||||
* from their raw upstream npm package. Also clean the legacy root-level DB
|
||||
* previously written by ClawX before OpenClaw 2026.7.1 moved state to state/.
|
||||
*/
|
||||
export function removePluginInstallRecordsFromSqlite(pluginIds: string[]): boolean {
|
||||
if (pluginIds.length === 0) return false;
|
||||
|
||||
const stateDir = resolveOpenClawStateDir();
|
||||
const sqlitePaths = [
|
||||
resolveOpenClawStateSqlitePath(),
|
||||
join(stateDir, 'openclaw.sqlite'),
|
||||
];
|
||||
let changed = false;
|
||||
|
||||
for (const sqlitePath of sqlitePaths) {
|
||||
if (!existsSync(sqlitePath)) continue;
|
||||
|
||||
let db: DatabaseSync | null = null;
|
||||
try {
|
||||
db = openStateDatabase(sqlitePath);
|
||||
const row = db.prepare(`
|
||||
SELECT install_records_json
|
||||
FROM installed_plugin_index
|
||||
WHERE index_key = ?
|
||||
`).get(INSTALLED_PLUGIN_INDEX_KEY) as { install_records_json?: string } | undefined;
|
||||
if (!row) continue;
|
||||
|
||||
const records = parseInstallRecordsJson(row.install_records_json);
|
||||
let databaseChanged = false;
|
||||
for (const pluginId of pluginIds) {
|
||||
if (Object.hasOwn(records, pluginId)) {
|
||||
delete records[pluginId];
|
||||
databaseChanged = true;
|
||||
}
|
||||
}
|
||||
if (!databaseChanged) continue;
|
||||
|
||||
const now = Date.now();
|
||||
db.prepare(`
|
||||
UPDATE installed_plugin_index
|
||||
SET install_records_json = ?,
|
||||
updated_at_ms = ?,
|
||||
generated_at_ms = ?
|
||||
WHERE index_key = ?
|
||||
`).run(JSON.stringify(records), now, now, INSTALLED_PLUGIN_INDEX_KEY);
|
||||
changed = true;
|
||||
} catch (error) {
|
||||
logger.warn(`[plugin] Failed to remove install metadata from ${sqlitePath}:`, error);
|
||||
} finally {
|
||||
db?.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
logger.info(`[plugin] Removed managed install metadata from SQLite for: ${pluginIds.join(', ')}`);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/** Ensure ~/.openclaw exists before first config write in fresh installs. */
|
||||
export function ensureOpenClawStateDirExists(): void {
|
||||
const stateDir = resolveOpenClawStateDir();
|
||||
|
||||
+300
-100
@@ -7,12 +7,17 @@
|
||||
*/
|
||||
import { app } from 'electron';
|
||||
import path from 'node:path';
|
||||
import { existsSync, cpSync, copyFileSync, statSync, mkdirSync, rmSync, readFileSync, writeFileSync, readdirSync, realpathSync } from 'node:fs';
|
||||
import { existsSync, cpSync, copyFileSync, statSync, lstatSync, mkdirSync, rmSync, readFileSync, writeFileSync, readdirSync, realpathSync, symlinkSync, unlinkSync } from 'node:fs';
|
||||
import { readdir, stat, copyFile, mkdir } from 'node:fs/promises';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { logger } from './logger';
|
||||
import { upsertPluginInstallRecordsIntoSqlite, ensureOpenClawStateDirExists } from './plugin-install-index';
|
||||
import { getOpenClawResolvedDir } from './paths';
|
||||
import {
|
||||
upsertPluginInstallRecordsIntoSqlite,
|
||||
removePluginInstallRecordsFromSqlite,
|
||||
ensureOpenClawStateDirExists,
|
||||
} from './plugin-install-index';
|
||||
|
||||
function normalizeFsPathForWindows(filePath: string): string {
|
||||
if (process.platform !== 'win32') return filePath;
|
||||
@@ -122,7 +127,7 @@ const MANIFEST_ID_FIXES: Record<string, string> = {
|
||||
/**
|
||||
* After a plugin has been copied to ~/.openclaw/extensions/<dir>, fix any
|
||||
* known manifest-ID mismatches so the Gateway can load the plugin.
|
||||
* Also patches package.json fields that the Gateway uses as "entry hints".
|
||||
* Also keeps package.json npm metadata usable by OpenClaw's repair planner.
|
||||
*/
|
||||
export function fixupPluginManifest(targetDir: string): void {
|
||||
// 1. Fix openclaw.plugin.json id
|
||||
@@ -131,45 +136,64 @@ export function fixupPluginManifest(targetDir: string): void {
|
||||
const raw = readFileSync(fsPath(manifestPath), 'utf-8');
|
||||
const manifest = JSON.parse(raw);
|
||||
const oldId = manifest.id as string | undefined;
|
||||
let modified = false;
|
||||
if (oldId && MANIFEST_ID_FIXES[oldId]) {
|
||||
const newId = MANIFEST_ID_FIXES[oldId];
|
||||
manifest.id = newId;
|
||||
writeFileSync(fsPath(manifestPath), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
||||
modified = true;
|
||||
logger.info(`[plugin] Fixed manifest ID: ${oldId} → ${newId}`);
|
||||
}
|
||||
|
||||
// OpenClaw 2026.7.1 treats configured channel plugins without a static
|
||||
// channelConfigs descriptor as stale/missing and invokes its npm repair
|
||||
// flow. The WeCom package has no descriptor upstream, so provide a
|
||||
// permissive schema that preserves ClawX's existing channel config fields.
|
||||
if (manifest.id === 'wecom' && !manifest.channelConfigs?.wecom) {
|
||||
manifest.channelConfigs = {
|
||||
...(manifest.channelConfigs ?? {}),
|
||||
wecom: {
|
||||
schema: {
|
||||
type: 'object',
|
||||
additionalProperties: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
modified = true;
|
||||
logger.info('[plugin] Added WeCom channelConfigs compatibility descriptor');
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
writeFileSync(fsPath(manifestPath), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
||||
}
|
||||
} catch {
|
||||
// manifest may not exist yet — ignore
|
||||
}
|
||||
|
||||
// 2. Fix package.json fields that Gateway uses as "entry hints"
|
||||
// 2. Keep package.json package-manager metadata valid
|
||||
const pkgPath = join(targetDir, 'package.json');
|
||||
try {
|
||||
const raw = readFileSync(fsPath(pkgPath), 'utf-8');
|
||||
const pkg = JSON.parse(raw);
|
||||
let modified = false;
|
||||
|
||||
// Check if the package name contains a legacy ID that needs fixing
|
||||
for (const [oldId, newId] of Object.entries(MANIFEST_ID_FIXES)) {
|
||||
if (typeof pkg.name === 'string' && pkg.name.includes(oldId)) {
|
||||
pkg.name = pkg.name.replace(oldId, newId);
|
||||
modified = true;
|
||||
}
|
||||
const install = pkg.openclaw?.install;
|
||||
if (install) {
|
||||
if (typeof install.npmSpec === 'string' && install.npmSpec.includes(oldId)) {
|
||||
install.npmSpec = install.npmSpec.replace(oldId, newId);
|
||||
modified = true;
|
||||
}
|
||||
if (typeof install.localPath === 'string' && install.localPath.includes(oldId)) {
|
||||
install.localPath = install.localPath.replace(oldId, newId);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
// Keep the real upstream npm package name/spec even though ClawX patches
|
||||
// the effective plugin id. Rewriting these to the non-existent
|
||||
// `@wecom/wecom` package makes OpenClaw's repair planner fail before the
|
||||
// Gateway starts. Restore metadata previously rewritten by older ClawX
|
||||
// compatibility code.
|
||||
if (pkg.name === '@wecom/wecom') {
|
||||
pkg.name = '@wecom/wecom-openclaw-plugin';
|
||||
modified = true;
|
||||
}
|
||||
const install = pkg.openclaw?.install;
|
||||
if (install?.npmSpec === '@wecom/wecom') {
|
||||
install.npmSpec = '@wecom/wecom-openclaw-plugin';
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
writeFileSync(fsPath(pkgPath), JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
||||
logger.info(`[plugin] Fixed package.json entry hints in ${targetDir}`);
|
||||
logger.info(`[plugin] Restored package.json npm metadata in ${targetDir}`);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
@@ -242,25 +266,56 @@ const PLUGIN_NPM_NAMES: Record<string, string> = {
|
||||
const OPENCLAW_CONFIG_PATH = join(homedir(), '.openclaw', 'openclaw.json');
|
||||
|
||||
/**
|
||||
* Official @openclaw/* channel plugins that ClawX mirrors into
|
||||
* ~/.openclaw/extensions/. OpenClaw 2026.6+ requires matching
|
||||
* plugins.installs metadata so trustedOfficialInstall is true and
|
||||
* runtime APIs such as openKeyedStore are available.
|
||||
* Channel plugins whose ClawX-managed mirrors need synchronized install
|
||||
* metadata. OpenClaw 2026.6+ reads these records from SQLite for trust checks;
|
||||
* OpenClaw 2026.7.1 also uses them to decide whether startup migrations should
|
||||
* update an installed plugin.
|
||||
*/
|
||||
const TRUSTED_OFFICIAL_EXTENSION_PLUGINS: Record<string, string> = {
|
||||
whatsapp: '@openclaw/whatsapp',
|
||||
discord: '@openclaw/discord',
|
||||
qqbot: '@openclaw/qqbot',
|
||||
type TrustedOfficialExtensionPlugin = {
|
||||
npmName: string;
|
||||
/** Effective manifest/config id when it differs from the mirror directory. */
|
||||
pluginId?: string;
|
||||
/** Path records keep OpenClaw from replacing a ClawX-patched mirror. */
|
||||
recordSource?: 'npm' | 'path';
|
||||
legacyPluginIds?: string[];
|
||||
};
|
||||
|
||||
type TrustedOfficialPluginInstallRecord = {
|
||||
source: 'npm';
|
||||
const TRUSTED_OFFICIAL_EXTENSION_PLUGINS: Record<string, TrustedOfficialExtensionPlugin> = {
|
||||
dingtalk: { npmName: '@soimy/dingtalk' },
|
||||
// WeCom intentionally runs under ClawX's legacy-compatible `wecom` id even
|
||||
// though the upstream package manifest still declares
|
||||
// `wecom-openclaw-plugin`. Keep it path-owned so startup migration does not
|
||||
// replace the compatibility-patched mirror with the raw npm package.
|
||||
wecom: {
|
||||
npmName: '@wecom/wecom-openclaw-plugin',
|
||||
recordSource: 'path',
|
||||
legacyPluginIds: ['wecom-openclaw-plugin'],
|
||||
},
|
||||
// @larksuite/openclaw-lark 2026.7.9 declares ./dist/index.js as `main`, but
|
||||
// publishes its runtime entry as ./index.js. OpenClaw 2026.7.1 rejects old
|
||||
// managed npm records during its post-core smoke check. Make ClawX's complete
|
||||
// mirror the canonical path-owned payload instead.
|
||||
'feishu-openclaw-plugin': {
|
||||
npmName: '@larksuite/openclaw-lark',
|
||||
pluginId: 'openclaw-lark',
|
||||
recordSource: 'path',
|
||||
legacyPluginIds: ['feishu-openclaw-plugin', 'feishu'],
|
||||
},
|
||||
whatsapp: { npmName: '@openclaw/whatsapp' },
|
||||
discord: { npmName: '@openclaw/discord' },
|
||||
qqbot: { npmName: '@openclaw/qqbot' },
|
||||
'openclaw-weixin': { npmName: '@tencent-weixin/openclaw-weixin' },
|
||||
'clawx-openai-image': {
|
||||
npmName: 'clawx-openai-image-plugin',
|
||||
recordSource: 'path',
|
||||
},
|
||||
};
|
||||
|
||||
type TrustedOfficialPluginInstallRecord = Record<string, unknown> & {
|
||||
source: 'npm' | 'path';
|
||||
spec: string;
|
||||
installPath: string;
|
||||
version: string;
|
||||
resolvedName: string;
|
||||
resolvedVersion: string;
|
||||
resolvedSpec: string;
|
||||
installedAt: string;
|
||||
};
|
||||
|
||||
@@ -277,52 +332,195 @@ function normalizePluginInstallPathForRecord(targetDir: string): string | null {
|
||||
function buildTrustedOfficialPluginInstallRecord(
|
||||
pluginDirName: string,
|
||||
targetDir: string,
|
||||
): TrustedOfficialPluginInstallRecord | null {
|
||||
const npmName = TRUSTED_OFFICIAL_EXTENSION_PLUGINS[pluginDirName];
|
||||
if (!npmName) return null;
|
||||
): { pluginId: string; record: TrustedOfficialPluginInstallRecord } | null {
|
||||
const definition = TRUSTED_OFFICIAL_EXTENSION_PLUGINS[pluginDirName];
|
||||
if (!definition) return null;
|
||||
|
||||
const version = readPluginVersion(join(targetDir, 'package.json'));
|
||||
const installPath = normalizePluginInstallPathForRecord(targetDir);
|
||||
if (!version || !installPath) return null;
|
||||
|
||||
const pluginId = definition.pluginId ?? pluginDirName;
|
||||
const installedAt = new Date().toISOString();
|
||||
if (definition.recordSource === 'path') {
|
||||
return {
|
||||
pluginId,
|
||||
record: {
|
||||
source: 'path',
|
||||
spec: targetDir,
|
||||
sourcePath: targetDir,
|
||||
installPath,
|
||||
version,
|
||||
installedAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
source: 'npm',
|
||||
spec: npmName,
|
||||
installPath,
|
||||
version,
|
||||
resolvedName: npmName,
|
||||
resolvedVersion: version,
|
||||
resolvedSpec: `${npmName}@${version}`,
|
||||
installedAt: new Date().toISOString(),
|
||||
pluginId,
|
||||
record: {
|
||||
source: 'npm',
|
||||
spec: definition.npmName,
|
||||
installPath,
|
||||
version,
|
||||
resolvedName: definition.npmName,
|
||||
resolvedVersion: version,
|
||||
resolvedSpec: `${definition.npmName}@${version}`,
|
||||
installedAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function pluginInstallRecordIds(pluginDirName: string): string[] {
|
||||
const definition = TRUSTED_OFFICIAL_EXTENSION_PLUGINS[pluginDirName];
|
||||
return [...new Set([
|
||||
pluginDirName,
|
||||
definition?.pluginId,
|
||||
...(definition?.legacyPluginIds ?? []),
|
||||
].filter((value): value is string => Boolean(value)))];
|
||||
}
|
||||
|
||||
function removeLegacyPluginInstallMetadataFromConfig(pluginIds: string[]): boolean {
|
||||
if (!existsSync(fsPath(OPENCLAW_CONFIG_PATH))) return false;
|
||||
|
||||
const raw = readFileSync(fsPath(OPENCLAW_CONFIG_PATH), 'utf-8');
|
||||
const config = JSON.parse(raw) as Record<string, unknown>;
|
||||
const plugins = config.plugins;
|
||||
if (!plugins || typeof plugins !== 'object' || Array.isArray(plugins)) return false;
|
||||
const pluginsRecord = plugins as Record<string, unknown>;
|
||||
const installs = pluginsRecord.installs;
|
||||
if (!installs || typeof installs !== 'object' || Array.isArray(installs)) return false;
|
||||
|
||||
const installsRecord = installs as Record<string, unknown>;
|
||||
const removedIds = pluginIds.filter((pluginId) => Object.hasOwn(installsRecord, pluginId));
|
||||
if (removedIds.length === 0) return false;
|
||||
for (const pluginId of removedIds) {
|
||||
delete installsRecord[pluginId];
|
||||
}
|
||||
if (Object.keys(installsRecord).length === 0) {
|
||||
delete pluginsRecord.installs;
|
||||
}
|
||||
writeFileSync(
|
||||
fsPath(OPENCLAW_CONFIG_PATH),
|
||||
`${JSON.stringify(config, null, 2)}\n`,
|
||||
'utf-8',
|
||||
);
|
||||
logger.info(`[plugin] Removed legacy config install metadata for: ${removedIds.join(', ')}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
function canonicalComparablePath(filePath: string): string {
|
||||
let resolved: string;
|
||||
try {
|
||||
resolved = realpathSync(fsPath(filePath));
|
||||
} catch {
|
||||
resolved = path.resolve(filePath);
|
||||
}
|
||||
const withoutLongPathPrefix = resolved.replace(/^\\\\\?\\UNC\\/i, '\\\\').replace(/^\\\\\?\\/i, '');
|
||||
return process.platform === 'win32' ? withoutLongPathPrefix.toLowerCase() : withoutLongPathPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Materialized mirrors live outside the bundled OpenClaw package tree, so
|
||||
* Node's normal package lookup cannot resolve their declared `openclaw` peer.
|
||||
* OpenClaw 2026.7.1 also audits this exact link before reporting Gateway ready.
|
||||
*/
|
||||
export function repairPluginOpenClawPeerLink(
|
||||
targetDir: string,
|
||||
openclawDir = getOpenClawResolvedDir(),
|
||||
): boolean {
|
||||
let packageJson: Record<string, unknown>;
|
||||
try {
|
||||
packageJson = JSON.parse(readFileSync(fsPath(join(targetDir, 'package.json')), 'utf-8')) as Record<string, unknown>;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
const peerDependencies = packageJson.peerDependencies;
|
||||
if (
|
||||
!peerDependencies
|
||||
|| typeof peerDependencies !== 'object'
|
||||
|| Array.isArray(peerDependencies)
|
||||
|| typeof (peerDependencies as Record<string, unknown>).openclaw !== 'string'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!existsSync(fsPath(join(openclawDir, 'package.json')))) {
|
||||
logger.warn(`[plugin] Cannot link OpenClaw peer for ${targetDir}: runtime package missing at ${openclawDir}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodeModulesDir = join(targetDir, 'node_modules');
|
||||
const linkPath = join(nodeModulesDir, 'openclaw');
|
||||
try {
|
||||
mkdirSync(fsPath(nodeModulesDir), { recursive: true });
|
||||
const nodeModulesStat = lstatSync(fsPath(nodeModulesDir));
|
||||
if (!nodeModulesStat.isDirectory() || nodeModulesStat.isSymbolicLink()) {
|
||||
logger.warn(`[plugin] Cannot link OpenClaw peer because ${nodeModulesDir} is not a real directory`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (canonicalComparablePath(linkPath) === canonicalComparablePath(openclawDir)) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to lstat/creation for a missing or broken link.
|
||||
}
|
||||
|
||||
let existing: ReturnType<typeof lstatSync> | null = null;
|
||||
try {
|
||||
existing = lstatSync(fsPath(linkPath));
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
||||
}
|
||||
if (existing) {
|
||||
if (existing.isSymbolicLink()) {
|
||||
unlinkSync(fsPath(linkPath));
|
||||
} else if (existing.isDirectory()) {
|
||||
let existingPackageName: unknown;
|
||||
try {
|
||||
existingPackageName = JSON.parse(
|
||||
readFileSync(fsPath(join(linkPath, 'package.json')), 'utf-8'),
|
||||
).name;
|
||||
} catch {
|
||||
existingPackageName = null;
|
||||
}
|
||||
if (existingPackageName !== 'openclaw') {
|
||||
logger.warn(`[plugin] Cannot replace non-OpenClaw peer directory at ${linkPath}`);
|
||||
return false;
|
||||
}
|
||||
rmSync(fsPath(linkPath), { recursive: true, force: true });
|
||||
} else {
|
||||
logger.warn(`[plugin] Cannot replace non-directory OpenClaw peer at ${linkPath}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
symlinkSync(openclawDir, fsPath(linkPath), 'junction');
|
||||
if (canonicalComparablePath(linkPath) !== canonicalComparablePath(openclawDir)) {
|
||||
logger.warn(`[plugin] OpenClaw peer link audit failed after creating ${linkPath}`);
|
||||
return false;
|
||||
}
|
||||
logger.info(`[plugin] Linked OpenClaw peer: ${linkPath} → ${openclawDir}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.warn(`[plugin] Failed to link OpenClaw peer for ${targetDir}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function persistTrustedOfficialPluginInstallRecordsToSqlite(
|
||||
records: Record<string, Record<string, unknown>>,
|
||||
): boolean {
|
||||
return upsertPluginInstallRecordsIntoSqlite(records);
|
||||
}
|
||||
|
||||
function trustedInstallRecordMatches(
|
||||
existing: unknown,
|
||||
expected: TrustedOfficialPluginInstallRecord,
|
||||
): boolean {
|
||||
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
||||
return false;
|
||||
}
|
||||
const record = existing as Record<string, unknown>;
|
||||
return record.source === expected.source
|
||||
&& record.spec === expected.spec
|
||||
&& record.installPath === expected.installPath
|
||||
&& record.version === expected.version
|
||||
&& record.resolvedName === expected.resolvedName
|
||||
&& record.resolvedVersion === expected.resolvedVersion
|
||||
&& record.resolvedSpec === expected.resolvedSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write or refresh plugins.installs.<id> for a ClawX-mirrored official plugin.
|
||||
* Also persists the record into openclaw.sqlite for OpenClaw 2026.6+ trust checks.
|
||||
* Persist a ClawX-mirrored plugin install record in OpenClaw's canonical SQLite
|
||||
* index. OpenClaw 2026.7.1 treats config-level plugins.installs as legacy
|
||||
* migration input, so remove that transient copy instead of recreating it.
|
||||
* Safe to call repeatedly; no-ops when metadata is already current.
|
||||
*/
|
||||
export function syncTrustedOfficialPluginInstallRecord(
|
||||
@@ -336,51 +534,53 @@ export function syncTrustedOfficialPluginInstallRecord(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Repair this even when install metadata already matches. A copied plugin's
|
||||
// node_modules intentionally excludes host peers, and OpenClaw's migration
|
||||
// smoke check runs before the Gateway can supply any runtime fallback.
|
||||
repairPluginOpenClawPeerLink(targetDir);
|
||||
|
||||
const recordIds = pluginInstallRecordIds(pluginDirName);
|
||||
let jsonChanged = false;
|
||||
try {
|
||||
ensureOpenClawStateDirExists();
|
||||
if (!existsSync(fsPath(OPENCLAW_CONFIG_PATH))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const raw = readFileSync(fsPath(OPENCLAW_CONFIG_PATH), 'utf-8');
|
||||
const config = JSON.parse(raw) as Record<string, unknown>;
|
||||
let plugins = config.plugins;
|
||||
if (!plugins || typeof plugins !== 'object' || Array.isArray(plugins)) {
|
||||
plugins = { enabled: true, installs: {} };
|
||||
config.plugins = plugins;
|
||||
}
|
||||
|
||||
const pluginsRecord = plugins as Record<string, unknown>;
|
||||
const installs = pluginsRecord.installs;
|
||||
const installsRecord = installs && typeof installs === 'object' && !Array.isArray(installs)
|
||||
? installs as Record<string, unknown>
|
||||
: {};
|
||||
|
||||
const existing = installsRecord[pluginDirName];
|
||||
if (!trustedInstallRecordMatches(existing, expected)) {
|
||||
installsRecord[pluginDirName] = expected;
|
||||
pluginsRecord.installs = installsRecord;
|
||||
writeFileSync(
|
||||
fsPath(OPENCLAW_CONFIG_PATH),
|
||||
`${JSON.stringify(config, null, 2)}\n`,
|
||||
'utf-8',
|
||||
);
|
||||
logger.info(`[plugin] Synced trusted install metadata for ${pluginDirName}`);
|
||||
jsonChanged = true;
|
||||
}
|
||||
jsonChanged = removeLegacyPluginInstallMetadataFromConfig(recordIds);
|
||||
} catch (error) {
|
||||
logger.warn(`[plugin] Failed to sync trusted install metadata for ${pluginDirName}:`, error);
|
||||
return false;
|
||||
// Keep the canonical SQLite repair available even if legacy config cleanup
|
||||
// cannot be completed in this pass.
|
||||
logger.warn(`[plugin] Failed to remove legacy install metadata for ${pluginDirName}:`, error);
|
||||
}
|
||||
|
||||
// Remove aliases left by older ClawX/OpenClaw ownership conventions, but do
|
||||
// not delete the canonical id first: upsert can replace npm/path ownership
|
||||
// atomically without creating a missing-record window.
|
||||
const staleRecordIds = recordIds.filter((pluginId) => pluginId !== expected.pluginId);
|
||||
const removedLegacyRecord = removePluginInstallRecordsFromSqlite(staleRecordIds);
|
||||
const sqliteChanged = persistTrustedOfficialPluginInstallRecordsToSqlite({
|
||||
[pluginDirName]: expected,
|
||||
[expected.pluginId]: expected.record,
|
||||
});
|
||||
return jsonChanged || removedLegacyRecord || sqliteChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove metadata for a ClawX mirror that is no longer configured. This must
|
||||
* run even when its extension directory is already missing: stale records are
|
||||
* themselves enough to fail OpenClaw's post-core payload smoke check.
|
||||
*/
|
||||
export function removeTrustedOfficialPluginInstallRecord(pluginDirName: string): boolean {
|
||||
const recordIds = pluginInstallRecordIds(pluginDirName);
|
||||
if (recordIds.length === 0) return false;
|
||||
|
||||
let jsonChanged = false;
|
||||
try {
|
||||
jsonChanged = removeLegacyPluginInstallMetadataFromConfig(recordIds);
|
||||
} catch (error) {
|
||||
logger.warn(`[plugin] Failed to remove stale config install metadata for ${pluginDirName}:`, error);
|
||||
}
|
||||
const sqliteChanged = removePluginInstallRecordsFromSqlite(recordIds);
|
||||
return jsonChanged || sqliteChanged;
|
||||
}
|
||||
|
||||
/** Repair trusted install metadata for all mirrored official plugins on disk. */
|
||||
/** Repair managed install metadata and host peer links for all mirrors on disk. */
|
||||
export function repairTrustedOfficialPluginInstallRecords(): void {
|
||||
for (const pluginDirName of Object.keys(TRUSTED_OFFICIAL_EXTENSION_PLUGINS)) {
|
||||
const targetDir = join(homedir(), '.openclaw', 'extensions', pluginDirName);
|
||||
|
||||
@@ -10,6 +10,6 @@ appliesTo:
|
||||
|
||||
Main owns ACP process, SDK, routing lifecycle, and serialization of operations on the shared ACP connection; Renderer owns semantic reduction into an in-memory timeline. Notifications emitted during `session/load` are returned as one generation-scoped raw batch and reduced in one Renderer state commit. Renderer may temporarily buffer matching host events during the IPC result handoff, while ordinary live prompt updates continue through host events. A pending prompt may retain a bounded Main routing context and Renderer timeline snapshot so navigation cannot drop its stream; those contexts must be keyed by session and generation, remain memory-only, and be released when the prompt settles. Permission requests are interactive only for an active prompt. Stale session generations are ignored, and ClawX does not persist a second ACP ledger or reduced Chat history.
|
||||
|
||||
ACP replay is the primary history authority. The only approved transcript-derived content supplements are best-effort recovery of asynchronous image-generation completions with proven `image_generate` context and recovery of explicit line-leading assistant OpenClaw `MEDIA:` attachment directives omitted by ACP. The general attachment exception does not require image-generation context, but it recovers only attachment references. A separate metadata-only supplement may annotate an ACP-replayed assistant turn with whole-turn duration because ACP `session/load` omits the original event timestamps; it cannot create turns or content. These exceptions remain marked and in memory; do not generalize them to bare paths, surrounding transcript prose, ordinary messages, tool cards, plans, permissions, thoughts, file activity, or any parallel history.
|
||||
ACP replay is the primary history authority. The only approved transcript-derived content supplements are best-effort recovery of asynchronous image-generation completions with proven `image_generate` context and recovery of explicit line-leading assistant OpenClaw `MEDIA:` attachment directives omitted by ACP. The general attachment exception does not require image-generation context, but it recovers only attachment references. When ACP replay for a cron session is completely empty, scheduled-task prompt and completion summaries may instead come from Main's typed cron-history host API. This cron exception must come from Gateway `cron.runs` (with a Main-owned legacy file fallback), be generation-scoped and in memory, and never replace or duplicate non-empty ACP replay. A separate metadata-only supplement may annotate an ACP-replayed assistant turn with whole-turn duration because ACP `session/load` omits the original event timestamps; it cannot create turns or content. These exceptions remain marked and in memory; do not generalize them to bare paths, surrounding transcript prose, arbitrary ordinary messages, tool cards, plans, permissions, thoughts, file activity, or any parallel persisted history.
|
||||
|
||||
Historical transcript reads are limited to the newest `1000` message records. A successful live prompt reads content immediately and retries exactly once after `1500 ms`. General attachment and timing alignment treat history as a suffix and match the binary-free OpenClaw prompt-text projection of structured ACP user blocks by duplicate occurrence from the tail; they must not parse or globally remove user-authored resource marker text. Attachment-only empty projections remain eligible, and live content alignment also requires the current optimistic user identity. Every asynchronous result must retain the same active session, generation, supplement operation and attempt, and live turn where applicable. Unmatched, ambiguous, superseded, or stale work cannot mutate the timeline or timing annotations.
|
||||
|
||||
@@ -84,6 +84,8 @@ Renderer code must not create direct Gateway WebSocket connections. Gateway fram
|
||||
|
||||
Channel/plugin migration behavior is also part of this scenario when ClawX rewrites OpenClaw config before Gateway launch. Upgrades must preserve single-owner channel registration for migrated plugin-backed channels such as Feishu/Lark.
|
||||
|
||||
Scheduled-task history is Main-owned backend data. Current OpenClaw versions must be queried through the Gateway `cron.runs` RPC; direct file reads are allowed only as a compatibility fallback for older file-backed runtimes. When a cron base session has no ACP replay, Renderer may project that typed host result into a generation-scoped, in-memory historical ACP timeline, but must not replace or duplicate non-empty ACP replay.
|
||||
|
||||
The Web Browser privileged bridge is also Main-owned: Renderer address and recovery navigation, data clearing, and external opening flow through the typed Host API. The artifact tab value `web-browser` identifies this Electron guest and remains distinct from the Workspace file browser value `browser`; UI ownership stays in `chat-workspace-and-navigation`. The durable guest contract is `harness/reference/web-browser.md`.
|
||||
|
||||
Gateway session-catalog subscription, normalization, ordered list/event replay, attention transitions, and reconnect recovery are documented in `harness/reference/sidebar-session-attention.md`.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
id: upgrade-openclaw-2026-7-1
|
||||
title: Upgrade the bundled OpenClaw runtime to 2026.7.1
|
||||
scenario: gateway-backend-communication
|
||||
taskType: runtime-bridge
|
||||
intent: Keep ClawX runtime and bundled channel plugins aligned with OpenClaw 2026.7.1 across supported platforms.
|
||||
touchedAreas:
|
||||
- .github/workflows/check.yml
|
||||
- package.json
|
||||
- pnpm-lock.yaml
|
||||
- scripts/download-bundled-node.mjs
|
||||
- scripts/bundle-openclaw.mjs
|
||||
- electron/services/acp-chat-service.ts
|
||||
- electron/services/cron-api.ts
|
||||
- src/lib/cron-session-history.ts
|
||||
- src/stores/acp-chat-session.ts
|
||||
- shared/acp-chat/types.ts
|
||||
- electron/utils/openclaw-upgrade-snapshot.ts
|
||||
- electron/utils/plugin-install-index.ts
|
||||
- electron/utils/plugin-install.ts
|
||||
- electron/gateway/config-sync.ts
|
||||
- electron/gateway/startup-recovery.ts
|
||||
- electron/gateway/startup-orchestrator.ts
|
||||
- electron/gateway/manager.ts
|
||||
- electron/gateway/process-policy.ts
|
||||
- tests/unit/acp-chat-service.test.ts
|
||||
- tests/unit/acp-chat-store.test.ts
|
||||
- tests/unit/cron-schedule.test.ts
|
||||
- tests/e2e/cron-run-live-status.spec.ts
|
||||
- tests/unit/gateway-startup-recovery.test.ts
|
||||
- tests/unit/gateway-startup-orchestrator.test.ts
|
||||
- tests/unit/openclaw-cli.test.ts
|
||||
- tests/unit/openclaw-bundle-config.test.ts
|
||||
- tests/unit/openclaw-upgrade-snapshot.test.ts
|
||||
- tests/unit/plugin-install-index.test.ts
|
||||
- tests/unit/plugin-install.test.ts
|
||||
- tests/unit/gateway-process-policy.test.ts
|
||||
- README.md
|
||||
- README.zh-CN.md
|
||||
- README.ja-JP.md
|
||||
- README.ru-RU.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
|
||||
expectedUserBehavior:
|
||||
- Existing OpenClaw 2026.6.10 configuration, authentication, sessions, and channel credentials remain usable after upgrade, with a one-time pre-migration snapshot of mutable config/auth/SQLite state.
|
||||
- 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.
|
||||
- Fatal runtime/SQLite incompatibility, EX_CONFIG exits, invalid migrations, and active migration leases do not enter unbounded Gateway restart loops.
|
||||
- ACP chat initializes, replays, prompts, cancels, requests permission, and forwards unknown ACP 1.1 session updates without dropping the NDJSON connection.
|
||||
- Cron sessions use OpenClaw 2026.7.1's SQLite-backed `cron.runs` history when ACP replay is empty, so immediate and scheduled executions show their prompt and completed summaries instead of an empty timeline.
|
||||
- Bundled channel plugins use versions compatible with OpenClaw 2026.7.1 while existing WeCom and Open Lark manifest-ID compatibility behavior remains unchanged.
|
||||
- Packaged builds use Electron and Windows Node runtimes that satisfy OpenClaw 2026.7.1 Node and SQLite requirements.
|
||||
requiredProfiles:
|
||||
- fast
|
||||
- comms
|
||||
requiredTests:
|
||||
- tests/unit/acp-chat-service.test.ts
|
||||
- tests/unit/acp-chat-store.test.ts
|
||||
- tests/unit/cron-schedule.test.ts
|
||||
- tests/e2e/cron-run-live-status.spec.ts
|
||||
- tests/unit/gateway-startup-recovery.test.ts
|
||||
- tests/unit/gateway-startup-orchestrator.test.ts
|
||||
- tests/unit/openclaw-cli.test.ts
|
||||
- tests/unit/openclaw-bundle-config.test.ts
|
||||
- tests/unit/openclaw-upgrade-snapshot.test.ts
|
||||
- tests/unit/plugin-install-index.test.ts
|
||||
- tests/unit/plugin-install.test.ts
|
||||
- tests/unit/channel-config.test.ts
|
||||
acceptance:
|
||||
- OpenClaw, ACP SDK, Electron, Windows Node, and official OpenClaw channel plugins are pinned to compatible runtime versions.
|
||||
- DingTalk is pinned to 3.6.6, WeCom to 2026.7.2, and Open Lark to 2026.7.9 without changing ClawX's effective manifest-ID mappings.
|
||||
- The lockfile resolves OpenClaw and all bundled channel plugins without incompatible peers or stale 2026.6.10 plugin packages.
|
||||
- Electron embeds Node 24.15.0 or newer within the Node 24 line and a WAL-reset-safe SQLite runtime.
|
||||
- The bundled Windows Node version satisfies OpenClaw 2026.7.1's declared engine range.
|
||||
- ClawX snapshots OpenClaw config, credentials, and SQLite databases with WAL/SHM sidecars once before the first 2026.7.1 prelaunch sync.
|
||||
- ClawX writes plugin install metadata to OpenClaw 2026.7.1's canonical `state/openclaw.sqlite` index, removes legacy config records, and represents the patched WeCom and official Feishu mirrors as local paths rather than stale npm-managed installs.
|
||||
- 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.
|
||||
- ACP 1.1 type checks, targeted runtime tests, communication regression checks, and harness validation pass.
|
||||
docs:
|
||||
required: true
|
||||
---
|
||||
|
||||
Use this task spec for the coordinated runtime, official plugin, lockfile, and
|
||||
Windows Node baseline upgrade required by OpenClaw 2026.7.1.
|
||||
+9
-9
@@ -88,7 +88,7 @@
|
||||
"postversion": "node scripts/post-version-push.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "~0.17.0",
|
||||
"@agentclientprotocol/sdk": "1.1.0",
|
||||
"electron-store": "^11.0.2",
|
||||
"electron-updater": "^6.8.3",
|
||||
"node-machine-id": "^1.1.12",
|
||||
@@ -106,12 +106,12 @@
|
||||
"@grammyjs/runner": "^2.0.3",
|
||||
"@grammyjs/transformer-throttler": "^1.2.1",
|
||||
"@homebridge/ciao": "^1.3.7",
|
||||
"@larksuite/openclaw-lark": "2026.6.10",
|
||||
"@larksuite/openclaw-lark": "2026.7.9",
|
||||
"@larksuiteoapi/node-sdk": "^1.61.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@openclaw/discord": "2026.6.10",
|
||||
"@openclaw/qqbot": "2026.6.10",
|
||||
"@openclaw/whatsapp": "2026.6.10",
|
||||
"@openclaw/discord": "2026.7.1",
|
||||
"@openclaw/qqbot": "2026.7.1",
|
||||
"@openclaw/whatsapp": "2026.7.1",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
@@ -126,7 +126,7 @@
|
||||
"@radix-ui/react-toast": "^1.2.15",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@sinclair/typebox": "^0.34.48",
|
||||
"@soimy/dingtalk": "^3.6.3",
|
||||
"@soimy/dingtalk": "3.6.6",
|
||||
"@tencent-connect/qqbot-connector": "^1.1.0",
|
||||
"@tencent-weixin/openclaw-weixin": "^2.4.6",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
@@ -139,7 +139,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
||||
"@typescript-eslint/parser": "^8.56.0",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"@wecom/wecom-openclaw-plugin": "^2026.6.23",
|
||||
"@wecom/wecom-openclaw-plugin": "2026.7.2",
|
||||
"@whiskeysockets/baileys": "7.0.0-rc.9",
|
||||
"acpx": "0.5.3",
|
||||
"autoprefixer": "^10.4.24",
|
||||
@@ -150,7 +150,7 @@
|
||||
"diff": "^9.0.0",
|
||||
"discord-api-types": "^0.38.47",
|
||||
"docx-preview": "^0.4.0",
|
||||
"electron": "^40.6.0",
|
||||
"electron": "40.10.6",
|
||||
"electron-builder": "^26.8.1",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
@@ -169,7 +169,7 @@
|
||||
"monaco-editor": "^0.55.1",
|
||||
"mpg123-decoder": "^1.0.3",
|
||||
"ms": "^2.1.3",
|
||||
"openclaw": "2026.6.10",
|
||||
"openclaw": "2026.7.1",
|
||||
"opusscript": "^0.1.1",
|
||||
"pdfjs-dist": "^5.7.284",
|
||||
"playwright-core": "1.59.1",
|
||||
|
||||
Generated
+213
-214
@@ -12,8 +12,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@agentclientprotocol/sdk':
|
||||
specifier: ~0.17.0
|
||||
version: 0.17.0(zod@4.4.3)
|
||||
specifier: 1.1.0
|
||||
version: 1.1.0(zod@4.4.3)
|
||||
electron-store:
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2
|
||||
@@ -61,8 +61,8 @@ importers:
|
||||
specifier: ^1.3.7
|
||||
version: 1.3.7
|
||||
'@larksuite/openclaw-lark':
|
||||
specifier: 2026.6.10
|
||||
version: 2026.6.10(openclaw@2026.6.10(encoding@0.1.13))
|
||||
specifier: 2026.7.9
|
||||
version: 2026.7.9(openclaw@2026.7.1(encoding@0.1.13))
|
||||
'@larksuiteoapi/node-sdk':
|
||||
specifier: ^1.61.1
|
||||
version: 1.62.0
|
||||
@@ -70,14 +70,14 @@ importers:
|
||||
specifier: ^4.7.0
|
||||
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.6.10
|
||||
version: 2026.6.10(openclaw@2026.6.10(encoding@0.1.13))
|
||||
specifier: 2026.7.1
|
||||
version: 2026.7.1(openclaw@2026.7.1(encoding@0.1.13))
|
||||
'@openclaw/qqbot':
|
||||
specifier: 2026.6.10
|
||||
version: 2026.6.10(openclaw@2026.6.10(encoding@0.1.13))
|
||||
specifier: 2026.7.1
|
||||
version: 2026.7.1(openclaw@2026.7.1(encoding@0.1.13))
|
||||
'@openclaw/whatsapp':
|
||||
specifier: 2026.6.10
|
||||
version: 2026.6.10(openclaw@2026.6.10(encoding@0.1.13))
|
||||
specifier: 2026.7.1
|
||||
version: 2026.7.1(openclaw@2026.7.1(encoding@0.1.13))
|
||||
'@playwright/test':
|
||||
specifier: ^1.56.1
|
||||
version: 1.59.0
|
||||
@@ -121,14 +121,14 @@ importers:
|
||||
specifier: ^0.34.48
|
||||
version: 0.34.48
|
||||
'@soimy/dingtalk':
|
||||
specifier: ^3.6.3
|
||||
version: 3.6.4(openclaw@2026.6.10(encoding@0.1.13))
|
||||
specifier: 3.6.6
|
||||
version: 3.6.6(openclaw@2026.7.1(encoding@0.1.13))
|
||||
'@tencent-connect/qqbot-connector':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
'@tencent-weixin/openclaw-weixin':
|
||||
specifier: ^2.4.6
|
||||
version: 2.4.6(openclaw@2026.6.10(encoding@0.1.13))
|
||||
version: 2.4.6(openclaw@2026.7.1(encoding@0.1.13))
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.9.1
|
||||
version: 6.9.1
|
||||
@@ -160,8 +160,8 @@ importers:
|
||||
specifier: ^5.1.4
|
||||
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.6.23
|
||||
version: 2026.6.23(openclaw@2026.6.10(encoding@0.1.13))
|
||||
specifier: 2026.7.2
|
||||
version: 2026.7.2(openclaw@2026.7.1(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)
|
||||
@@ -193,8 +193,8 @@ importers:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
electron:
|
||||
specifier: ^40.6.0
|
||||
version: 40.8.4
|
||||
specifier: 40.10.6
|
||||
version: 40.10.6
|
||||
electron-builder:
|
||||
specifier: ^26.8.1
|
||||
version: 26.8.1(electron-builder-squirrel-windows@26.8.1)
|
||||
@@ -250,8 +250,8 @@ importers:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
openclaw:
|
||||
specifier: 2026.6.10
|
||||
version: 2026.6.10(encoding@0.1.13)
|
||||
specifier: 2026.7.1
|
||||
version: 2026.7.1(encoding@0.1.13)
|
||||
opusscript:
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1
|
||||
@@ -370,8 +370,8 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
'@agentclientprotocol/sdk@0.22.1':
|
||||
resolution: {integrity: sha512-DfqXtl/8gO9NImq094MTaCXEU2vkhh6v7q/kT+9UjZxUqj8hYaya2OjLVIqn16MzNHcXEpShTR2RIauLSYeDQQ==}
|
||||
'@agentclientprotocol/sdk@1.1.0':
|
||||
resolution: {integrity: sha512-NT2KqphUJ3w6EksUL51ZhJgIYgq/ZLGcBPkyMKgRSO5PMVwe9DnKKX+Htnvk6KHh6dUuh34UHK4gKp+4te1Mdg==}
|
||||
peerDependencies:
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
@@ -379,8 +379,8 @@ packages:
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@anthropic-ai/sdk@0.100.1':
|
||||
resolution: {integrity: sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==}
|
||||
'@anthropic-ai/sdk@0.109.1':
|
||||
resolution: {integrity: sha512-q9OnEKLr5H9nxSuXdgDgJhxfYMiE+AaUEBze2Gk91UcaaLnsN+Lx5fbCYywiqurU/APLdwv23x03Wm6WN3EBsg==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
@@ -508,15 +508,15 @@ packages:
|
||||
'@clack/core@1.2.0':
|
||||
resolution: {integrity: sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==}
|
||||
|
||||
'@clack/core@1.3.1':
|
||||
resolution: {integrity: sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA==}
|
||||
'@clack/core@1.4.2':
|
||||
resolution: {integrity: sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==}
|
||||
engines: {node: '>= 20.12.0'}
|
||||
|
||||
'@clack/prompts@1.2.0':
|
||||
resolution: {integrity: sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==}
|
||||
|
||||
'@clack/prompts@1.4.0':
|
||||
resolution: {integrity: sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA==}
|
||||
'@clack/prompts@1.6.0':
|
||||
resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==}
|
||||
engines: {node: '>= 20.12.0'}
|
||||
|
||||
'@cloudflare/workers-types@4.20260405.1':
|
||||
@@ -566,10 +566,14 @@ packages:
|
||||
resolution: {integrity: sha512-3yJ255e4ag3wfZu/DSxeOZK1UtnqNxnspmLaQetGT0pDkThNZoHs+Zg6dgZZ19JEVomXygvfHn9lNpICZuYtEA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@earendil-works/pi-tui@0.78.0':
|
||||
resolution: {integrity: sha512-3a705FnsVVUhAyceShNB3kS2rpxcxLcx+hqB0u6MMMpHwQGbW+m++MqA6r7eOzq/8FLx5e3vDh38h/SVTk2qzw==}
|
||||
'@earendil-works/pi-tui@0.80.3':
|
||||
resolution: {integrity: sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==}
|
||||
engines: {node: '>=22.19.0'}
|
||||
|
||||
'@electron-internal/extract-zip@1.0.4':
|
||||
resolution: {integrity: sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@electron/asar@3.4.1':
|
||||
resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
@@ -579,14 +583,14 @@ packages:
|
||||
resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==}
|
||||
hasBin: true
|
||||
|
||||
'@electron/get@2.0.3':
|
||||
resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@electron/get@3.1.0':
|
||||
resolution: {integrity: sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@electron/get@5.0.0':
|
||||
resolution: {integrity: sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@electron/notarize@2.5.0':
|
||||
resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@@ -841,8 +845,8 @@ packages:
|
||||
'@floating-ui/utils@0.2.11':
|
||||
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
|
||||
|
||||
'@google/genai@2.7.0':
|
||||
resolution: {integrity: sha512-tv0DRtcndt2oEhBYy+5mA0TaXH98+L1Gt0AP9unBfH7DP20KhB7+O3QqAN1Lz+laMARGTHS7BFQSNpLbl4gm1g==}
|
||||
'@google/genai@2.10.0':
|
||||
resolution: {integrity: sha512-e4cFxj3tiuMtsgOT4G9c1hXyGJhg7/Buj7VVeBacRY3fRtkRZZ59Q3nuVp2xbq8BGQXLXCDB253qMhklMOeUDg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
'@modelcontextprotocol/sdk': ^1.25.2
|
||||
@@ -865,8 +869,8 @@ packages:
|
||||
'@grammyjs/types@3.26.0':
|
||||
resolution: {integrity: sha512-jlnyfxfev/2o68HlvAGRocAXgdPPX5QabG7jZlbqC2r9DZyWBfzTlg+nu3O3Fy4EhgLWu28hZ/8wr7DsNamP9A==}
|
||||
|
||||
'@grammyjs/types@3.27.3':
|
||||
resolution: {integrity: sha512-yUKMLliGsGbnxu96YUJ7km7B0zy4PzeH/Jvti5705R/LeKDMqkDV4DckMSt+OrliWQpTwQljHE0QLol5zgxBkg==}
|
||||
'@grammyjs/types@3.28.0':
|
||||
resolution: {integrity: sha512-4JvXCdxRZHCje0M4gHzLwtB4bLno3WD28xd8CNfk4POWIu73BFnSvGeW6OQ5gPem4eYTEwkD9yDaXssixl6tMQ==}
|
||||
|
||||
'@hapi/boom@9.1.4':
|
||||
resolution: {integrity: sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==}
|
||||
@@ -1205,8 +1209,8 @@ packages:
|
||||
'@kurkle/color@0.3.4':
|
||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||
|
||||
'@larksuite/openclaw-lark@2026.6.10':
|
||||
resolution: {integrity: sha512-OdNePiG88jRIUrRAx0h3bF2o5UxLD4c9zlK5Wfn1xirYLic6koGpx7xoRFAgvCMd15DzLQQtekkwO6+xkDupBw==}
|
||||
'@larksuite/openclaw-lark@2026.7.9':
|
||||
resolution: {integrity: sha512-3rRkGU85CtFOROT12ysd9cCc/w7RHM/f9OQuDIvNYF5oaun3NM6Z03CPvLkhI+GccJxxM0Lfr64LUeZCYj8Ezw==}
|
||||
engines: {node: '>=22'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -1262,8 +1266,13 @@ packages:
|
||||
resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@mistralai/mistralai@2.2.5':
|
||||
resolution: {integrity: sha512-ATbWzKkNzNAZ+gtw9MI/c/ULTMG80tKUiRNIbQFfg4OP0uEZZpTfXZeBCNfs5Dq0uqMQ/tQWc4o6RRJQtMrpDA==}
|
||||
'@mistralai/mistralai@2.4.0':
|
||||
resolution: {integrity: sha512-t6hCx242MTGolB76CI+17jDtPIe/bzLsMdUTMMoMn9Qo1h02N2G5jQYHmKDGU3X//OgR2wvngTD7tO6tPp5poQ==}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
|
||||
'@modelcontextprotocol/sdk@1.29.0':
|
||||
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
|
||||
@@ -1466,10 +1475,14 @@ packages:
|
||||
resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
'@openclaw/discord@2026.6.10':
|
||||
resolution: {integrity: sha512-NKp/j00l+rk5PC0Lv/0fOIiiQJ1c/OpG9471zqXUDKQie6pQ1Fi9KUZUouyoTMmfLh/n4S0CkEMqrON40eBKXA==}
|
||||
'@openclaw/ai@2026.7.1':
|
||||
resolution: {integrity: sha512-FsKy5DXSHf4qyN8Huoz/10HZRgoEwLF4uk8UWaCafaIler+q5Fsl51HcrIqIrEe0S38OT7LOaxnR++MOshAlmw==}
|
||||
engines: {node: '>=22.19.0'}
|
||||
|
||||
'@openclaw/discord@2026.7.1':
|
||||
resolution: {integrity: sha512-tZfdC1YA8oVLvc2BK1w0F6rUljS5ugCOp2uWe0vPsbG1fbzVVIO4V32RoqZznGHe5u2R9u4n1aV5Z/qa1m2oFg==}
|
||||
peerDependencies:
|
||||
openclaw: '>=2026.6.10'
|
||||
openclaw: '>=2026.7.1'
|
||||
peerDependenciesMeta:
|
||||
openclaw:
|
||||
optional: true
|
||||
@@ -1481,9 +1494,9 @@ packages:
|
||||
- undici
|
||||
- ws
|
||||
|
||||
'@openclaw/fs-safe@0.3.0':
|
||||
resolution: {integrity: sha512-uIBE441CIt1kIURoP9qRGKZ8LkGyfD9ZzeESjwAd29ZPWtghws/5GR3Pjb67jKdcJHP1I6roNXcvnhzAU7lHlA==}
|
||||
engines: {node: '>=20.11'}
|
||||
'@openclaw/fs-safe@0.4.1':
|
||||
resolution: {integrity: sha512-hQi+BxO10KdRFlYUot1syC+hTaUnGeQNdqX5kwkKJig8CFq1tKsYJLPm+zkiiGsSKOprPAquQl/txejEhpKPgg==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@openclaw/proxyline@0.3.3':
|
||||
resolution: {integrity: sha512-sftHnW69NHQqLjCxBTvQ8f/eQl+peZ5pHCBQtuTWBbeuYRHZ0/GXVTmw/O/YKsShMbqPWhJB0UYtPPdvCUSS8w==}
|
||||
@@ -1491,10 +1504,10 @@ packages:
|
||||
peerDependencies:
|
||||
undici: '>=8.3.0 <9'
|
||||
|
||||
'@openclaw/qqbot@2026.6.10':
|
||||
resolution: {integrity: sha512-6G1yvO+pzvdO2ByfyuefAFVj2mW4urCpEN5BTxevXvmMuE7+AXhu8F0Z4aeIQOnUvBzzu7ZyDQtQ+gOA1trmkw==}
|
||||
'@openclaw/qqbot@2026.7.1':
|
||||
resolution: {integrity: sha512-h5Ak9UyoIzMl1qs88vYwPBdtXDXfUZmta4Tjo1JVtZjEFF/3zl8JOwAfUv739mgBwJ2D7frHz+yvdLXkSV0lfA==}
|
||||
peerDependencies:
|
||||
openclaw: '>=2026.6.10'
|
||||
openclaw: '>=2026.7.1'
|
||||
peerDependenciesMeta:
|
||||
openclaw:
|
||||
optional: true
|
||||
@@ -1505,10 +1518,10 @@ packages:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@openclaw/whatsapp@2026.6.10':
|
||||
resolution: {integrity: sha512-k/XrRdZY77SHrdaRwJOEB7/JRbjp4yVgGD/ZNyakjTMqo32XRVtwPBUnj7726rW8Kl5yyOMQQLKFiD9MDfhmPQ==}
|
||||
'@openclaw/whatsapp@2026.7.1':
|
||||
resolution: {integrity: sha512-wLY/Omc5fleRpl2lKGN8sxt/8hYfHGwLRezmWsk8oCbea5pRKUPE6ZX+wJO1O52NOJkAGCuiXvS7x0qIeKxXbQ==}
|
||||
peerDependencies:
|
||||
openclaw: '>=2026.6.10'
|
||||
openclaw: '>=2026.7.1'
|
||||
peerDependenciesMeta:
|
||||
openclaw:
|
||||
optional: true
|
||||
@@ -1517,6 +1530,10 @@ packages:
|
||||
- baileys
|
||||
- typebox
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.43.0':
|
||||
resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@pinojs/redact@0.4.0':
|
||||
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||
|
||||
@@ -2277,8 +2294,8 @@ packages:
|
||||
resolution: {integrity: sha512-oBN+msHzPnm1M5DDx3wVD7iBwpNXFUtkh2MrAbUJu0OhKjliLChi28hq++mu1+qdMpAVQO5JKAvQQxYVbyneiw==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@soimy/dingtalk@3.6.4':
|
||||
resolution: {integrity: sha512-SgX0hbN6b4F1wQ50pZZEKCQLhno7GIHA9AIWslAilVFVLu5e6DIBx3uWPbx6u1uBF31yaSV2TGq8AoeNVEAqbA==}
|
||||
'@soimy/dingtalk@3.6.6':
|
||||
resolution: {integrity: sha512-qbzxQWjgQBlORwMWt9kI1FG1uBm1V7BRKZyMO72xwaSWEhH+i+hbnZjARmPhxYAv0DpbZp0RWe0h7Jno0HnADg==}
|
||||
peerDependencies:
|
||||
openclaw: '>=2026.3.28'
|
||||
peerDependenciesMeta:
|
||||
@@ -2457,9 +2474,6 @@ packages:
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.57.2':
|
||||
resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -2572,8 +2586,8 @@ packages:
|
||||
'@wecom/aibot-node-sdk@1.0.6':
|
||||
resolution: {integrity: sha512-WZJN3Q+s+94Qjc0VW8d5W1cVkA3emYxiqf+mNRO9UEHoF40puHvizreNMtudjFhm7mmkYiK5ue/QzNiCk+xwLA==}
|
||||
|
||||
'@wecom/wecom-openclaw-plugin@2026.6.23':
|
||||
resolution: {integrity: sha512-IYxLDLiiYmL/v3oN4WJOvRD+5yis+CS+Rr7mcjGs24UBmRukMr+i0UBghWRW9ub2jnitP6Gs0uqhLMf0kZ9/Tw==}
|
||||
'@wecom/wecom-openclaw-plugin@2026.7.2':
|
||||
resolution: {integrity: sha512-7kqdBIOF3SgDDoBoFtO6jxnxofbYSgbKdxZDNabD0y0jg2xKcVqlXZOOJ9+XQho/QOtIFrnRH2IRnPukFEYwJg==}
|
||||
peerDependencies:
|
||||
openclaw: '>=2026.3.28'
|
||||
peerDependenciesMeta:
|
||||
@@ -2906,9 +2920,6 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
|
||||
@@ -3091,6 +3102,10 @@ packages:
|
||||
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@15.0.0:
|
||||
resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
commander@4.1.1:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -3407,9 +3422,9 @@ packages:
|
||||
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
electron@40.8.4:
|
||||
resolution: {integrity: sha512-7AoSakFr+g2CTukHDS79cqNiaWPoD8bQ4kIahwUUVv0O5fy4BfZawVCxOFLc61POq8xDvqMSDKPfeFXK/Coc5g==}
|
||||
engines: {node: '>= 12.20.55'}
|
||||
electron@40.10.6:
|
||||
resolution: {integrity: sha512-TGjlkOU9Lg6K4KjDbsErywCWCIDaNgLh0q+xj0nlpRoQhevI7VBIxBTtJI/V30lypyLAaXMpnP9O9jui1/qRFw==}
|
||||
engines: {node: '>= 22.12.0'}
|
||||
hasBin: true
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
@@ -3597,11 +3612,6 @@ packages:
|
||||
extend@3.0.2:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
|
||||
extract-zip@2.0.1:
|
||||
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
|
||||
engines: {node: '>= 10.17.0'}
|
||||
hasBin: true
|
||||
|
||||
extsprintf@1.4.1:
|
||||
resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
|
||||
engines: {'0': node >=0.6.0}
|
||||
@@ -3659,9 +3669,6 @@ packages:
|
||||
fault@2.0.1:
|
||||
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
||||
|
||||
fd-slicer@1.1.0:
|
||||
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -3909,8 +3916,8 @@ packages:
|
||||
resolution: {integrity: sha512-1AdCge+AkjSdp2FwfICSFnVbl8Mq3KVHJDy+DgTI9+D6keJ0zWALPRKas5jv/8psiCzL4N2cEOcGW7O45Kn39g==}
|
||||
engines: {node: ^12.20.0 || >=14.13.1}
|
||||
|
||||
grammy@1.43.0:
|
||||
resolution: {integrity: sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==}
|
||||
grammy@1.44.0:
|
||||
resolution: {integrity: sha512-gGVykS5+c5f1tPV97LuU6IDRMawE2NzpwM9pNz58HQ35IZXDnYL3VOLvNzYognPSeBIOSzQXRu5w96V0aY8y8A==}
|
||||
engines: {node: ^12.20.0 || >=14.13.1}
|
||||
|
||||
has-flag@4.0.0:
|
||||
@@ -4295,7 +4302,7 @@ packages:
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
libsignal@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/bcea72df9ec34d9d9140ab30619cf479c7c144c7:
|
||||
resolution: {gitHosted: true, tarball: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/bcea72df9ec34d9d9140ab30619cf479c7c144c7}
|
||||
resolution: {gitHosted: true, integrity: sha512-KmRMuAYSfd4upRD0xOgKbURxRXNE8X8NtgmkUOJFXOn9ESSCXguIZ7PJu8OBJIr7KGww41Vf/Pbgko+PgWQ55g==, tarball: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/bcea72df9ec34d9d9140ab30619cf479c7c144c7}
|
||||
version: 6.0.0
|
||||
|
||||
lie@3.3.0:
|
||||
@@ -4404,9 +4411,9 @@ packages:
|
||||
engines: {node: '>= 18'}
|
||||
hasBin: true
|
||||
|
||||
marked@15.0.12:
|
||||
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
|
||||
engines: {node: '>= 18'}
|
||||
marked@18.0.5:
|
||||
resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==}
|
||||
engines: {node: '>= 20'}
|
||||
hasBin: true
|
||||
|
||||
matcher@3.0.0:
|
||||
@@ -4848,21 +4855,29 @@ packages:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
openai@6.39.1:
|
||||
resolution: {integrity: sha512-z3dO9fEWOXBzlXynVb/xZ/tujzUjFWQWn3C0n0mw6Vo0zJTbEkaN4b2cLWjhJ6haJQx8LlREoafHRl+Gu/Hl+A==}
|
||||
hasBin: true
|
||||
openai@6.45.0:
|
||||
resolution: {integrity: sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==}
|
||||
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
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
openclaw@2026.6.10:
|
||||
resolution: {integrity: sha512-LcooND2tBQw8A+kc1Ujltu3lg30bJ0w7XaeRy7eYzobb8BBdcW6DOGbwJL4vpj1vl9+gjRceOtlh5nh9OARcug==}
|
||||
engines: {node: '>=22.19.0'}
|
||||
openclaw@2026.7.1:
|
||||
resolution: {integrity: sha512-ge/Xss99CHAjPL/ikmH/UFoiOrjcxDB4sW3y9mhyCD+dYW3wzV7TKbAVdkrXFgAG2d2BjpJofP97zUZ+umxo8g==}
|
||||
engines: {node: '>=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0'}
|
||||
hasBin: true
|
||||
|
||||
option@0.2.4:
|
||||
@@ -5003,9 +5018,6 @@ packages:
|
||||
resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -5053,8 +5065,8 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright-core@1.60.0:
|
||||
resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
|
||||
playwright-core@1.61.1:
|
||||
resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -5257,8 +5269,8 @@ packages:
|
||||
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
quickjs-wasi@3.0.0:
|
||||
resolution: {integrity: sha512-X7ouKC4ZVf9bXQ8rsE7+L6TeBbesejAJH61x16xRaGAQGfBHHRcniWgzJZZVtHc8rS9yVsY+Tvk8/usAosg4bg==}
|
||||
quickjs-wasi@3.0.2:
|
||||
resolution: {integrity: sha512-SyfPzlrfz67/kv0SogmQgW4c2I1klkLcbvj9Y2gc1h7+VylmvuGevFljLXibGKajKJKiJV29d4S6FQLA6Sc80A==}
|
||||
|
||||
range-parser@1.2.1:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
@@ -5850,8 +5862,8 @@ packages:
|
||||
resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tar@7.5.16:
|
||||
resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==}
|
||||
tar@7.5.19:
|
||||
resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
teex@1.0.1:
|
||||
@@ -5998,8 +6010,8 @@ packages:
|
||||
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
typebox@1.1.39:
|
||||
resolution: {integrity: sha512-vj0afVtOfLQvv0GR0VxVagYxsXN64btL7Z9XoaG0ZggH3mruMMkOO6hXdgMsjCY3shZgEvooAWVeznQVs5c43w==}
|
||||
typebox@1.3.3:
|
||||
resolution: {integrity: sha512-URXGUE31PJDQC+PtRMJeLdF4kmmOdFoVPikPCtV2oOIhUpNpppEdIz7W8bH8cFYPYHdDpaRvqwdegMTmHliudg==}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
@@ -6263,8 +6275,8 @@ packages:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
web-tree-sitter@0.26.9:
|
||||
resolution: {integrity: sha512-YJwSHANl6XFgeEjB8nitgj0qZYt5gkIesJ4w2srS2wcLB4GUa4xcOkM0YaMsU6WNR53YVIkDSY7Ej4pf3IXtCA==}
|
||||
web-tree-sitter@0.26.10:
|
||||
resolution: {integrity: sha512-vengBGYS7FpAerkR3o04oBL4L8MkVmjawK50AFBu7v0HZBkmF9ZavPGKoXLSSmRhp7T/YgsJ7joAS3yAxHPEqQ==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
@@ -6347,18 +6359,6 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
ws@8.20.1:
|
||||
resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
ws@8.21.0:
|
||||
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -6444,9 +6444,6 @@ packages:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yauzl@2.10.0:
|
||||
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6506,13 +6503,13 @@ snapshots:
|
||||
dependencies:
|
||||
zod: 4.4.3
|
||||
|
||||
'@agentclientprotocol/sdk@0.22.1(zod@4.4.3)':
|
||||
'@agentclientprotocol/sdk@1.1.0(zod@4.4.3)':
|
||||
dependencies:
|
||||
zod: 4.4.3
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@anthropic-ai/sdk@0.100.1(zod@4.4.3)':
|
||||
'@anthropic-ai/sdk@0.109.1(zod@4.4.3)':
|
||||
dependencies:
|
||||
json-schema-to-ts: 3.1.1
|
||||
standardwebhooks: 1.0.0
|
||||
@@ -6700,7 +6697,7 @@ snapshots:
|
||||
fast-wrap-ansi: 0.1.6
|
||||
sisteransi: 1.0.5
|
||||
|
||||
'@clack/core@1.3.1':
|
||||
'@clack/core@1.4.2':
|
||||
dependencies:
|
||||
fast-wrap-ansi: 0.2.0
|
||||
sisteransi: 1.0.5
|
||||
@@ -6712,9 +6709,9 @@ snapshots:
|
||||
fast-wrap-ansi: 0.1.6
|
||||
sisteransi: 1.0.5
|
||||
|
||||
'@clack/prompts@1.4.0':
|
||||
'@clack/prompts@1.6.0':
|
||||
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
|
||||
@@ -6769,10 +6766,12 @@ snapshots:
|
||||
- opusscript
|
||||
- utf-8-validate
|
||||
|
||||
'@earendil-works/pi-tui@0.78.0':
|
||||
'@earendil-works/pi-tui@0.80.3':
|
||||
dependencies:
|
||||
get-east-asian-width: 1.6.0
|
||||
marked: 15.0.12
|
||||
marked: 18.0.5
|
||||
|
||||
'@electron-internal/extract-zip@1.0.4': {}
|
||||
|
||||
'@electron/asar@3.4.1':
|
||||
dependencies:
|
||||
@@ -6786,7 +6785,7 @@ snapshots:
|
||||
fs-extra: 9.1.0
|
||||
minimist: 1.2.8
|
||||
|
||||
'@electron/get@2.0.3':
|
||||
'@electron/get@3.1.0':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
env-paths: 2.2.1
|
||||
@@ -6800,17 +6799,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@electron/get@3.1.0':
|
||||
'@electron/get@5.0.0':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
env-paths: 2.2.1
|
||||
fs-extra: 8.1.0
|
||||
got: 11.8.6
|
||||
env-paths: 3.0.0
|
||||
graceful-fs: 4.2.11
|
||||
progress: 2.0.3
|
||||
semver: 6.3.1
|
||||
semver: 7.7.4
|
||||
sumchecker: 3.0.1
|
||||
optionalDependencies:
|
||||
global-agent: 3.0.0
|
||||
undici: 7.24.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -7025,7 +7023,7 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.11': {}
|
||||
|
||||
'@google/genai@2.7.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))':
|
||||
'@google/genai@2.10.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))':
|
||||
dependencies:
|
||||
google-auth-library: 10.6.2
|
||||
p-retry: 4.6.2
|
||||
@@ -7043,24 +7041,24 @@ snapshots:
|
||||
abort-controller: 3.0.0
|
||||
grammy: 1.42.0(encoding@0.1.13)
|
||||
|
||||
'@grammyjs/runner@2.0.3(grammy@1.43.0(encoding@0.1.13))':
|
||||
'@grammyjs/runner@2.0.3(grammy@1.44.0(encoding@0.1.13))':
|
||||
dependencies:
|
||||
abort-controller: 3.0.0
|
||||
grammy: 1.43.0(encoding@0.1.13)
|
||||
grammy: 1.44.0(encoding@0.1.13)
|
||||
|
||||
'@grammyjs/transformer-throttler@1.2.1(grammy@1.42.0(encoding@0.1.13))':
|
||||
dependencies:
|
||||
bottleneck: 2.19.5
|
||||
grammy: 1.42.0(encoding@0.1.13)
|
||||
|
||||
'@grammyjs/transformer-throttler@1.2.1(grammy@1.43.0(encoding@0.1.13))':
|
||||
'@grammyjs/transformer-throttler@1.2.1(grammy@1.44.0(encoding@0.1.13))':
|
||||
dependencies:
|
||||
bottleneck: 2.19.5
|
||||
grammy: 1.43.0(encoding@0.1.13)
|
||||
grammy: 1.44.0(encoding@0.1.13)
|
||||
|
||||
'@grammyjs/types@3.26.0': {}
|
||||
|
||||
'@grammyjs/types@3.27.3': {}
|
||||
'@grammyjs/types@3.28.0': {}
|
||||
|
||||
'@hapi/boom@9.1.4':
|
||||
dependencies:
|
||||
@@ -7490,7 +7488,7 @@ snapshots:
|
||||
|
||||
'@kurkle/color@0.3.4': {}
|
||||
|
||||
'@larksuite/openclaw-lark@2026.6.10(openclaw@2026.6.10(encoding@0.1.13))':
|
||||
'@larksuite/openclaw-lark@2026.7.9(openclaw@2026.7.1(encoding@0.1.13))':
|
||||
dependencies:
|
||||
'@larksuiteoapi/node-sdk': 1.66.1
|
||||
'@sinclair/typebox': 0.34.49
|
||||
@@ -7498,7 +7496,7 @@ snapshots:
|
||||
undici-types: 8.3.0
|
||||
zod: 4.4.3
|
||||
optionalDependencies:
|
||||
openclaw: 2026.6.10(encoding@0.1.13)
|
||||
openclaw: 2026.7.1(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- debug
|
||||
@@ -7572,8 +7570,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@mistralai/mistralai@2.2.5':
|
||||
'@mistralai/mistralai@2.4.0':
|
||||
dependencies:
|
||||
'@opentelemetry/semantic-conventions': 1.43.0
|
||||
ws: 8.21.0
|
||||
zod: 4.4.3
|
||||
zod-to-json-schema: 3.25.1(zod@4.4.3)
|
||||
@@ -7745,26 +7744,48 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.7.4
|
||||
|
||||
'@openclaw/discord@2026.6.10(openclaw@2026.6.10(encoding@0.1.13))':
|
||||
optionalDependencies:
|
||||
openclaw: 2026.6.10(encoding@0.1.13)
|
||||
'@openclaw/ai@2026.7.1(@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))
|
||||
'@mistralai/mistralai': 2.4.0
|
||||
openai: 6.45.0(ws@8.21.0)(zod@4.4.3)
|
||||
partial-json: 0.1.7
|
||||
typebox: 1.3.3
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-provider-node'
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- '@opentelemetry/api'
|
||||
- '@smithy/hash-node'
|
||||
- '@smithy/signature-v4'
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@openclaw/fs-safe@0.3.0':
|
||||
'@openclaw/discord@2026.7.1(openclaw@2026.7.1(encoding@0.1.13))':
|
||||
optionalDependencies:
|
||||
openclaw: 2026.7.1(encoding@0.1.13)
|
||||
|
||||
'@openclaw/fs-safe@0.4.1':
|
||||
optionalDependencies:
|
||||
jszip: 3.10.1
|
||||
tar: 7.5.13
|
||||
tar: 7.5.19
|
||||
|
||||
'@openclaw/proxyline@0.3.3(undici@8.5.0)':
|
||||
dependencies:
|
||||
undici: 8.5.0
|
||||
|
||||
'@openclaw/qqbot@2026.6.10(openclaw@2026.6.10(encoding@0.1.13))':
|
||||
'@openclaw/qqbot@2026.7.1(openclaw@2026.7.1(encoding@0.1.13))':
|
||||
optionalDependencies:
|
||||
openclaw: 2026.6.10(encoding@0.1.13)
|
||||
openclaw: 2026.7.1(encoding@0.1.13)
|
||||
|
||||
'@openclaw/whatsapp@2026.6.10(openclaw@2026.6.10(encoding@0.1.13))':
|
||||
'@openclaw/whatsapp@2026.7.1(openclaw@2026.7.1(encoding@0.1.13))':
|
||||
optionalDependencies:
|
||||
openclaw: 2026.6.10(encoding@0.1.13)
|
||||
openclaw: 2026.7.1(encoding@0.1.13)
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.43.0': {}
|
||||
|
||||
'@pinojs/redact@0.4.0': {}
|
||||
|
||||
@@ -8413,7 +8434,7 @@ snapshots:
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
|
||||
'@soimy/dingtalk@3.6.4(openclaw@2026.6.10(encoding@0.1.13))':
|
||||
'@soimy/dingtalk@3.6.6(openclaw@2026.7.1(encoding@0.1.13))':
|
||||
dependencies:
|
||||
axios: 1.13.6(debug@4.4.3)
|
||||
dingtalk-stream: 2.1.5
|
||||
@@ -8422,7 +8443,7 @@ snapshots:
|
||||
pdf-parse: 2.4.5
|
||||
zod: 4.4.3
|
||||
optionalDependencies:
|
||||
openclaw: 2026.6.10(encoding@0.1.13)
|
||||
openclaw: 2026.7.1(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- debug
|
||||
@@ -8441,9 +8462,9 @@ snapshots:
|
||||
dependencies:
|
||||
qrcode-terminal: 0.12.0
|
||||
|
||||
'@tencent-weixin/openclaw-weixin@2.4.6(openclaw@2026.6.10(encoding@0.1.13))':
|
||||
'@tencent-weixin/openclaw-weixin@2.4.6(openclaw@2026.7.1(encoding@0.1.13))':
|
||||
dependencies:
|
||||
openclaw: 2026.6.10(encoding@0.1.13)
|
||||
openclaw: 2026.7.1(encoding@0.1.13)
|
||||
qrcode-terminal: 0.12.0
|
||||
zod: 4.4.3
|
||||
|
||||
@@ -8630,11 +8651,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
optional: true
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
@@ -8813,7 +8829,7 @@ snapshots:
|
||||
- debug
|
||||
- utf-8-validate
|
||||
|
||||
'@wecom/wecom-openclaw-plugin@2026.6.23(openclaw@2026.6.10(encoding@0.1.13))':
|
||||
'@wecom/wecom-openclaw-plugin@2026.7.2(openclaw@2026.7.1(encoding@0.1.13))':
|
||||
dependencies:
|
||||
'@wecom/aibot-node-sdk': 1.0.6
|
||||
fast-xml-parser: 5.7.3
|
||||
@@ -8821,7 +8837,7 @@ snapshots:
|
||||
undici: 7.24.6
|
||||
zod: 4.4.3
|
||||
optionalDependencies:
|
||||
openclaw: 2026.6.10(encoding@0.1.13)
|
||||
openclaw: 2026.7.1(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- debug
|
||||
@@ -9187,8 +9203,6 @@ snapshots:
|
||||
node-releases: 2.0.36
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||
|
||||
buffer-crc32@0.2.13: {}
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
@@ -9395,6 +9409,8 @@ snapshots:
|
||||
|
||||
commander@14.0.3: {}
|
||||
|
||||
commander@15.0.0: {}
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
commander@5.1.0: {}
|
||||
@@ -9572,7 +9588,7 @@ snapshots:
|
||||
dependencies:
|
||||
axios: 1.13.6(debug@4.4.3)
|
||||
debug: 4.4.3
|
||||
ws: 8.20.1
|
||||
ws: 8.21.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
@@ -9754,11 +9770,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron@40.8.4:
|
||||
electron@40.10.6:
|
||||
dependencies:
|
||||
'@electron/get': 2.0.3
|
||||
'@electron-internal/extract-zip': 1.0.4
|
||||
'@electron/get': 5.0.0
|
||||
'@types/node': 24.12.0
|
||||
extract-zip: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -9998,16 +10014,6 @@ snapshots:
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
extract-zip@2.0.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
get-stream: 5.2.0
|
||||
yauzl: 2.10.0
|
||||
optionalDependencies:
|
||||
'@types/yauzl': 2.10.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
extsprintf@1.4.1:
|
||||
optional: true
|
||||
|
||||
@@ -10071,10 +10077,6 @@ snapshots:
|
||||
dependencies:
|
||||
format: 0.2.2
|
||||
|
||||
fd-slicer@1.1.0:
|
||||
dependencies:
|
||||
pend: 1.2.0
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
@@ -10378,9 +10380,9 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
grammy@1.43.0(encoding@0.1.13):
|
||||
grammy@1.44.0(encoding@0.1.13):
|
||||
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(encoding@0.1.13)
|
||||
@@ -10950,7 +10952,7 @@ snapshots:
|
||||
|
||||
marked@14.0.0: {}
|
||||
|
||||
marked@15.0.12: {}
|
||||
marked@18.0.5: {}
|
||||
|
||||
matcher@3.0.0:
|
||||
dependencies:
|
||||
@@ -11611,39 +11613,41 @@ snapshots:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
openai@6.39.1(ws@8.21.0)(zod@4.4.3):
|
||||
openai@6.45.0(ws@8.21.0)(zod@4.4.3):
|
||||
optionalDependencies:
|
||||
ws: 8.21.0
|
||||
zod: 4.4.3
|
||||
|
||||
openclaw@2026.6.10(encoding@0.1.13):
|
||||
openclaw@2026.7.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@agentclientprotocol/sdk': 0.22.1(zod@4.4.3)
|
||||
'@anthropic-ai/sdk': 0.100.1(zod@4.4.3)
|
||||
'@clack/core': 1.3.1
|
||||
'@clack/prompts': 1.4.0
|
||||
'@earendil-works/pi-tui': 0.78.0
|
||||
'@google/genai': 2.7.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))
|
||||
'@grammyjs/runner': 2.0.3(grammy@1.43.0(encoding@0.1.13))
|
||||
'@grammyjs/transformer-throttler': 1.2.1(grammy@1.43.0(encoding@0.1.13))
|
||||
'@agentclientprotocol/sdk': 1.1.0(zod@4.4.3)
|
||||
'@anthropic-ai/sdk': 0.109.1(zod@4.4.3)
|
||||
'@clack/core': 1.4.2
|
||||
'@clack/prompts': 1.6.0
|
||||
'@earendil-works/pi-tui': 0.80.3
|
||||
'@google/genai': 2.10.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))
|
||||
'@grammyjs/runner': 2.0.3(grammy@1.44.0(encoding@0.1.13))
|
||||
'@grammyjs/transformer-throttler': 1.2.1(grammy@1.44.0(encoding@0.1.13))
|
||||
'@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(zod@4.4.3)
|
||||
'@mozilla/readability': 0.6.0
|
||||
'@openclaw/fs-safe': 0.3.0
|
||||
'@openclaw/ai': 2026.7.1(@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
|
||||
chalk: 5.6.2
|
||||
chokidar: 5.0.0
|
||||
clawpdf: 0.3.0
|
||||
commander: 14.0.3
|
||||
commander: 15.0.0
|
||||
croner: 10.0.1
|
||||
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(encoding@0.1.13)
|
||||
grammy: 1.44.0(encoding@0.1.13)
|
||||
highlight.js: 11.11.1
|
||||
hosted-git-info: 10.1.1
|
||||
ignore: 7.0.5
|
||||
@@ -11654,28 +11658,32 @@ snapshots:
|
||||
linkedom: 0.18.12
|
||||
minimatch: 10.2.5
|
||||
node-edge-tts: 1.2.10
|
||||
openai: 6.39.1(ws@8.21.0)(zod@4.4.3)
|
||||
openai: 6.45.0(ws@8.21.0)(zod@4.4.3)
|
||||
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
|
||||
quickjs-wasi: 3.0.2
|
||||
rastermill: 0.3.1
|
||||
tar: 7.5.16
|
||||
tar: 7.5.19
|
||||
tree-sitter-bash: 0.25.1
|
||||
tslog: 4.10.2
|
||||
typebox: 1.1.39
|
||||
typebox: 1.3.3
|
||||
typescript: 6.0.3
|
||||
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
|
||||
optionalDependencies:
|
||||
sqlite-vec: 0.1.9
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-provider-node'
|
||||
- '@cfworker/json-schema'
|
||||
- '@opentelemetry/api'
|
||||
- '@smithy/hash-node'
|
||||
- '@smithy/signature-v4'
|
||||
- bufferutil
|
||||
- canvas
|
||||
- encoding
|
||||
@@ -11824,8 +11832,6 @@ snapshots:
|
||||
|
||||
pe-library@0.4.1: {}
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.2: {}
|
||||
@@ -11867,7 +11873,7 @@ snapshots:
|
||||
|
||||
playwright-core@1.59.1: {}
|
||||
|
||||
playwright-core@1.60.0: {}
|
||||
playwright-core@1.61.1: {}
|
||||
|
||||
playwright@1.59.0:
|
||||
dependencies:
|
||||
@@ -12048,7 +12054,7 @@ snapshots:
|
||||
|
||||
quick-lru@5.1.1: {}
|
||||
|
||||
quickjs-wasi@3.0.0: {}
|
||||
quickjs-wasi@3.0.2: {}
|
||||
|
||||
range-parser@1.2.1: {}
|
||||
|
||||
@@ -12802,7 +12808,7 @@ snapshots:
|
||||
minizlib: 3.1.0
|
||||
yallist: 5.0.0
|
||||
|
||||
tar@7.5.16:
|
||||
tar@7.5.19:
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
chownr: 3.0.0
|
||||
@@ -12950,7 +12956,7 @@ snapshots:
|
||||
media-typer: 1.1.0
|
||||
mime-types: 3.0.2
|
||||
|
||||
typebox@1.1.39: {}
|
||||
typebox@1.3.3: {}
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
@@ -13172,7 +13178,7 @@ snapshots:
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
web-tree-sitter@0.26.9: {}
|
||||
web-tree-sitter@0.26.10: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
@@ -13240,8 +13246,6 @@ snapshots:
|
||||
|
||||
ws@8.20.0: {}
|
||||
|
||||
ws@8.20.1: {}
|
||||
|
||||
ws@8.21.0: {}
|
||||
|
||||
xlsx@0.18.5:
|
||||
@@ -13319,11 +13323,6 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yauzl@2.10.0:
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
fd-slicer: 1.1.0
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@4.4.3):
|
||||
|
||||
+11
-60
@@ -841,30 +841,6 @@ function patchBrokenModules(nodeModulesDir) {
|
||||
}
|
||||
}
|
||||
|
||||
function findFirstFileByName(rootDir, matcher) {
|
||||
const stack = [rootDir];
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop();
|
||||
let entries = [];
|
||||
try {
|
||||
entries = fs.readdirSync(current, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
stack.push(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (entry.isFile() && matcher.test(entry.name)) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findFilesByName(rootDir, matcher) {
|
||||
const matches = [];
|
||||
const stack = [rootDir];
|
||||
@@ -891,36 +867,11 @@ function findFilesByName(rootDir, matcher) {
|
||||
}
|
||||
|
||||
function patchBundledRuntime(outputDir) {
|
||||
const replacePatches = [
|
||||
{
|
||||
label: 'workspace command runner',
|
||||
target: () => findFirstFileByName(path.join(outputDir, 'dist'), /^workspace-.*\.js$/),
|
||||
search: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
|
||||
\t\tstdio,
|
||||
\t\tcwd,
|
||||
\t\tenv: resolvedEnv,
|
||||
\t\twindowsVerbatimArguments,
|
||||
\t\t...shouldSpawnWithShell({
|
||||
\t\t\tresolvedCommand,
|
||||
\t\t\tplatform: process$1.platform
|
||||
\t\t}) ? { shell: true } : {}
|
||||
\t});`,
|
||||
replace: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
|
||||
\t\tstdio,
|
||||
\t\tcwd,
|
||||
\t\tenv: resolvedEnv,
|
||||
\t\twindowsVerbatimArguments,
|
||||
\t\twindowsHide: true,
|
||||
\t\t...shouldSpawnWithShell({
|
||||
\t\t\tresolvedCommand,
|
||||
\t\t\tplatform: process$1.platform
|
||||
\t\t}) ? { shell: true } : {}
|
||||
\t});`,
|
||||
},
|
||||
// Note: OpenClaw 3.31 removed the hash-suffixed agent-scope-*.js, chrome-*.js,
|
||||
// and qmd-manager-*.js files from dist/plugin-sdk/. Patches for those spawn
|
||||
// sites are no longer needed — the runtime now uses windowsHide natively.
|
||||
];
|
||||
// OpenClaw 2026.7.1 routes ordinary child-process execution through
|
||||
// resolveChildProcessInvocation(), which already sets windowsHide=true.
|
||||
// PTY execution remains patched below because node-pty follows a separate
|
||||
// launch path and is disabled on Windows in ClawX packaged builds.
|
||||
const replacePatches = [];
|
||||
|
||||
let count = 0;
|
||||
for (const patch of replacePatches) {
|
||||
@@ -949,21 +900,21 @@ function patchBundledRuntime(outputDir) {
|
||||
|
||||
const ptyTargets = findFilesByName(
|
||||
path.join(outputDir, 'dist'),
|
||||
/^(subagent-registry|reply|pi-embedded)-.*\.js$/,
|
||||
/^(supervisor|bash-tools)-.*\.js$/,
|
||||
);
|
||||
const ptyPatches = [
|
||||
{
|
||||
label: 'pty launcher windowsHide',
|
||||
search: `\tconst pty = spawn(params.shell, params.args, {
|
||||
search: `\tconst pty = spawn(preparedSpawn.command, preparedSpawn.args, {
|
||||
\t\tcwd: params.cwd,
|
||||
\t\tenv: params.env ? toStringEnv(params.env) : void 0,
|
||||
\t\tenv: preparedSpawn.env ? toStringEnv(preparedSpawn.env) : void 0,
|
||||
\t\tname: params.name ?? process.env.TERM ?? "xterm-256color",
|
||||
\t\tcols: params.cols ?? 120,
|
||||
\t\trows: params.rows ?? 30
|
||||
\t});`,
|
||||
replace: `\tconst pty = spawn(params.shell, params.args, {
|
||||
replace: `\tconst pty = spawn(preparedSpawn.command, preparedSpawn.args, {
|
||||
\t\tcwd: params.cwd,
|
||||
\t\tenv: params.env ? toStringEnv(params.env) : void 0,
|
||||
\t\tenv: preparedSpawn.env ? toStringEnv(preparedSpawn.env) : void 0,
|
||||
\t\tname: params.name ?? process.env.TERM ?? "xterm-256color",
|
||||
\t\tcols: params.cols ?? 120,
|
||||
\t\trows: params.rows ?? 30,
|
||||
@@ -996,7 +947,7 @@ function patchBundledRuntime(outputDir) {
|
||||
}
|
||||
}
|
||||
if (!matchedAny) {
|
||||
echo` ⚠️ Skipped patch for ${patch.label}: expected source snippet not found`;
|
||||
throw new Error(`Required OpenClaw 2026.7.1 patch not found: ${patch.label}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import 'zx/globals';
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname, '..');
|
||||
const NODE_VERSION = '22.19.0';
|
||||
const NODE_VERSION = '22.22.3';
|
||||
const BASE_URL = `https://nodejs.org/dist/v${NODE_VERSION}`;
|
||||
const OUTPUT_BASE = path.join(ROOT_DIR, 'resources', 'bin');
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SessionNotification } from '@agentclientprotocol/sdk';
|
||||
import { hostApi } from '@/lib/host-api';
|
||||
import type { RawMessage } from '@/stores/chat/types';
|
||||
|
||||
@@ -5,3 +6,50 @@ export async function fetchCronSessionHistory(sessionKey: string, limit = 200):
|
||||
const response = await hostApi.cron.sessionHistory({ sessionKey, limit });
|
||||
return Array.isArray(response.messages) ? response.messages : [];
|
||||
}
|
||||
|
||||
function getCronHistoryMessageText(content: RawMessage['content']): string {
|
||||
if (typeof content === 'string') return content.trim();
|
||||
if (!Array.isArray(content)) return '';
|
||||
return content
|
||||
.map((block) => {
|
||||
if (!block || typeof block !== 'object') return '';
|
||||
const value = block as { type?: unknown; text?: unknown };
|
||||
return value.type === 'text' && typeof value.text === 'string' ? value.text : '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Project authoritative cron run summaries into ACP's in-memory timeline shape.
|
||||
* This is used only when OpenClaw returns no ACP replay for a cron base session.
|
||||
*/
|
||||
export function buildCronHistoryAcpNotifications(
|
||||
sessionKey: string,
|
||||
messages: RawMessage[],
|
||||
): SessionNotification[] {
|
||||
return messages.flatMap((message, index): SessionNotification[] => {
|
||||
const text = getCronHistoryMessageText(message.content);
|
||||
if (!text) return [];
|
||||
const messageId = String(message.id ?? `cron-history-${index}`);
|
||||
if (message.role === 'user') {
|
||||
return [{
|
||||
sessionId: sessionKey,
|
||||
update: {
|
||||
sessionUpdate: 'user_message_chunk',
|
||||
messageId,
|
||||
content: { type: 'text', text },
|
||||
},
|
||||
}];
|
||||
}
|
||||
return [{
|
||||
sessionId: sessionKey,
|
||||
update: {
|
||||
sessionUpdate: 'agent_message_chunk',
|
||||
messageId,
|
||||
content: { type: 'text', text },
|
||||
},
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,9 +40,11 @@ import { hashOpenClawMediaDiagnostic, type OpenClawMediaCandidate } from '@/lib/
|
||||
import { openClawResourceLinkPromptText } from '@/lib/acp/openclaw-prompt-compat';
|
||||
import { fetchOpenClawTranscriptSupplement } from '@/lib/acp/transcript-supplement';
|
||||
import { alignHistoricalTurnTimings, type AcpTurnTiming } from '@/lib/acp/turn-timings';
|
||||
import { buildCronHistoryAcpNotifications, fetchCronSessionHistory } from '@/lib/cron-session-history';
|
||||
import { hostApi } from '@/lib/host-api';
|
||||
import { hostEvents } from '@/lib/host-events';
|
||||
import type { AcpTimelineSnapshot, MessageSegmentItem, PermissionItem, RenderPart } from '@/lib/acp/timeline-types';
|
||||
import { isCronSessionKey } from './chat/cron-session-utils';
|
||||
|
||||
const EMPTY_SESSION_ID = '';
|
||||
const CANCEL_PERMISSION_OPTION_ID = '__cancelled__';
|
||||
@@ -1063,6 +1065,22 @@ export const useAcpChatSessionStore = create<AcpChatSessionState>((set, get) =>
|
||||
}
|
||||
|
||||
const generation = result.generation ?? state.generation;
|
||||
let cronHistoryNotifications: ReturnType<typeof buildCronHistoryAcpNotifications> = [];
|
||||
if (!input.createIfMissing && isCronSessionKey(input.sessionKey)) {
|
||||
try {
|
||||
const cronMessages = await fetchCronSessionHistory(input.sessionKey, 200);
|
||||
cronHistoryNotifications = buildCronHistoryAcpNotifications(input.sessionKey, cronMessages);
|
||||
} catch {
|
||||
// ACP replay remains authoritative; cron history is best-effort only.
|
||||
}
|
||||
state = get();
|
||||
if (
|
||||
loadRequestSeq !== requestId
|
||||
|| state.activeSessionKey !== input.sessionKey
|
||||
|| state.workspaceRoot !== input.workspaceRoot
|
||||
|| state.cwd !== input.cwd
|
||||
) return false;
|
||||
}
|
||||
const sessionUpdates = [
|
||||
...(result.sessionUpdates ?? []),
|
||||
...(pendingLoadUpdates.get(generation) ?? []),
|
||||
@@ -1089,6 +1107,11 @@ export const useAcpChatSessionStore = create<AcpChatSessionState>((set, get) =>
|
||||
for (const event of sessionUpdates) {
|
||||
timeline = applyAcpSessionUpdate(timeline, event.notification, { historical: !!event.historical });
|
||||
}
|
||||
if (timeline.itemOrder.length === 0) {
|
||||
for (const notification of cronHistoryNotifications) {
|
||||
timeline = applyAcpSessionUpdate(timeline, notification, { historical: true });
|
||||
}
|
||||
}
|
||||
const pendingAttachments = newPendingAttachments(
|
||||
createEmptyAcpTimeline(input.sessionKey, generation),
|
||||
timeline,
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { ElectronApplication } from '@playwright/test';
|
||||
import { closeElectronApp, expect, getStableWindow, installIpcMocks, test } from './fixtures/electron';
|
||||
import {
|
||||
closeElectronApp,
|
||||
expect,
|
||||
getRecordedHostInvocations,
|
||||
getStableWindow,
|
||||
installIpcMocks,
|
||||
test,
|
||||
} from './fixtures/electron';
|
||||
|
||||
const MAIN_SESSION_KEY = 'agent:main:main';
|
||||
const CRON_BASE_KEY = 'agent:main:cron:job-cron-live';
|
||||
@@ -160,6 +167,76 @@ test.describe('ClawX cron run live status', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('shows cron run summaries when ACP replay is empty', async ({ launchElectronApp }) => {
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
|
||||
try {
|
||||
const cronSession = {
|
||||
key: CRON_BASE_KEY,
|
||||
displayName: 'Cron: 喝水提醒',
|
||||
label: 'Cron: 喝水提醒',
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
await installIpcMocks(app, {
|
||||
recordHostInvocations: true,
|
||||
gatewayStatus: { state: 'running', port: 18789, pid: 12345, gatewayReady: true },
|
||||
gatewayRpc: {
|
||||
[stableStringify(['sessions.list', {}])]: {
|
||||
success: true,
|
||||
result: {
|
||||
sessions: [
|
||||
{ key: MAIN_SESSION_KEY, displayName: 'main' },
|
||||
cronSession,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
hostApi: {
|
||||
...acpLoadMocks(MAIN_SESSION_KEY),
|
||||
...acpLoadMocks(CRON_BASE_KEY),
|
||||
[stableStringify(['cron', 'sessionHistory', { sessionKey: CRON_BASE_KEY, limit: 200 }])]: {
|
||||
messages: [
|
||||
{ id: 'cron-prompt', role: 'user', content: '提醒我喝水', timestamp: Date.now() - 5000 },
|
||||
{ id: 'cron-result', role: 'assistant', content: '该喝水了!💧', timestamp: Date.now() },
|
||||
],
|
||||
},
|
||||
[stableStringify(['/api/gateway/status', 'GET'])]: {
|
||||
ok: true,
|
||||
data: { status: 200, ok: true, json: { state: 'running', port: 18789, pid: 12345, gatewayReady: true } },
|
||||
},
|
||||
[stableStringify(['/api/agents', 'GET'])]: {
|
||||
ok: true,
|
||||
data: { status: 200, ok: true, json: { success: true, agents: [{ id: 'main', name: 'Main', workspace: DEFAULT_WORKSPACE, mainSessionKey: MAIN_SESSION_KEY }] } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const page = await getStableWindow(app);
|
||||
try {
|
||||
await page.reload();
|
||||
} catch (error) {
|
||||
if (!String(error).includes('ERR_FILE_NOT_FOUND')) throw error;
|
||||
}
|
||||
|
||||
await expect(page.getByTestId('main-layout')).toBeVisible({ timeout: 30_000 });
|
||||
const cronSidebarButton = page.getByTestId(`sidebar-session-${CRON_BASE_KEY}`);
|
||||
await expect(cronSidebarButton).toBeVisible({ timeout: 30_000 });
|
||||
await cronSidebarButton.click();
|
||||
|
||||
await expect.poll(async () => (await getRecordedHostInvocations(app)).some((call) => (
|
||||
call.module === 'cron'
|
||||
&& call.action === 'sessionHistory'
|
||||
&& call.payload?.sessionKey === CRON_BASE_KEY
|
||||
))).toBe(true);
|
||||
await expect(page.getByText('提醒我喝水')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.getByText('该喝水了!💧')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.getByTestId('acp-chat-empty-state')).toHaveCount(0);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('adopts an already-running cron run joined mid-flight (no run.started received)', async ({ launchElectronApp }) => {
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
|
||||
|
||||
@@ -239,8 +239,7 @@ describe('AcpChatService', () => {
|
||||
expect(connection.prompt).toHaveBeenCalledWith({
|
||||
sessionId: 'acp-session-1',
|
||||
prompt: [{ type: 'text', text: 'hello' }],
|
||||
messageId: 'msg-1',
|
||||
_meta: { sessionKey: 'agent:pi:session-123', prefixCwd: true },
|
||||
_meta: { sessionKey: 'agent:pi:session-123', prefixCwd: true, messageId: 'msg-1' },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -285,7 +284,9 @@ describe('AcpChatService', () => {
|
||||
sessionUpdate: 'agent_message_chunk',
|
||||
messageId: 'msg-1',
|
||||
content: { type: 'text', text: 'hello' },
|
||||
futureExtensionField: { retained: true },
|
||||
},
|
||||
_meta: { futureTopLevelMetadata: 'retained' },
|
||||
} as never);
|
||||
await service.client.sessionUpdate({
|
||||
sessionId: 'agent:other:s2',
|
||||
@@ -306,7 +307,9 @@ describe('AcpChatService', () => {
|
||||
sessionUpdate: 'agent_message_chunk',
|
||||
messageId: 'msg-1',
|
||||
content: { type: 'text', text: 'hello' },
|
||||
futureExtensionField: { retained: true },
|
||||
},
|
||||
_meta: { futureTopLevelMetadata: 'retained' },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1139,7 +1142,6 @@ describe('AcpChatService', () => {
|
||||
|
||||
expect(connection.prompt).toHaveBeenCalledWith({
|
||||
sessionId: 'agent:pi:s1',
|
||||
messageId: 'msg-user-1',
|
||||
prompt: [
|
||||
{ type: 'text', text: 'Inspect attachments' },
|
||||
{
|
||||
@@ -1157,7 +1159,7 @@ describe('AcpChatService', () => {
|
||||
_meta: { clawx: { stagingId: 'staged-notes' } },
|
||||
},
|
||||
],
|
||||
_meta: { sessionKey: 'agent:pi:s1', prefixCwd: true },
|
||||
_meta: { sessionKey: 'agent:pi:s1', prefixCwd: true, messageId: 'msg-user-1' },
|
||||
});
|
||||
} finally {
|
||||
rmSync(imagePath, { force: true });
|
||||
|
||||
@@ -10,6 +10,7 @@ const hostApiMock = vi.hoisted(() => ({
|
||||
mediaThumbnails: vi.fn(),
|
||||
recordAcpTrace: vi.fn(),
|
||||
sessionsHistory: vi.fn(),
|
||||
cronSessionHistory: vi.fn(),
|
||||
sessionTurnTimings: vi.fn(),
|
||||
resolveAttachment: vi.fn(),
|
||||
}));
|
||||
@@ -55,6 +56,9 @@ vi.mock('@/lib/host-api', () => ({
|
||||
history: hostApiMock.sessionsHistory,
|
||||
turnTimings: hostApiMock.sessionTurnTimings,
|
||||
},
|
||||
cron: {
|
||||
sessionHistory: hostApiMock.cronSessionHistory,
|
||||
},
|
||||
files: {
|
||||
resolveAttachment: hostApiMock.resolveAttachment,
|
||||
},
|
||||
@@ -132,6 +136,7 @@ describe('ACP Chat store', () => {
|
||||
hostApiMock.mediaThumbnails.mockReset().mockResolvedValue({});
|
||||
hostApiMock.recordAcpTrace.mockReset().mockResolvedValue({ success: true });
|
||||
hostApiMock.sessionsHistory.mockReset().mockResolvedValue({ success: true, messages: [] });
|
||||
hostApiMock.cronSessionHistory.mockReset().mockResolvedValue({ messages: [] });
|
||||
hostApiMock.sessionTurnTimings.mockReset().mockResolvedValue({ success: true, timings: [] });
|
||||
hostApiMock.resolveAttachment.mockReset().mockImplementation(async (payload: {
|
||||
ref: { sessionKey: string; generation: number; uri: string };
|
||||
@@ -161,6 +166,65 @@ describe('ACP Chat store', () => {
|
||||
hostEventsMock.onChatRuntimeEvent.mockClear();
|
||||
});
|
||||
|
||||
it('projects cron history when ACP replay is empty', async () => {
|
||||
hostApiMock.cronSessionHistory.mockResolvedValue({
|
||||
messages: [
|
||||
{ id: 'cron-prompt', role: 'user', content: '提醒我喝水', timestamp: 1_700_000_000_000 },
|
||||
{ id: 'cron-result', role: 'assistant', content: '该喝水了!💧', timestamp: 1_700_000_005_000 },
|
||||
],
|
||||
});
|
||||
const { useAcpChatSessionStore } = await importStore();
|
||||
|
||||
const loaded = await useAcpChatSessionStore.getState().loadSession({
|
||||
sessionKey: 'agent:main:cron:job-water',
|
||||
workspaceRoot: '/repo-a',
|
||||
cwd: '/repo-a',
|
||||
});
|
||||
|
||||
expect(loaded).toBe(true);
|
||||
expect(hostApiMock.cronSessionHistory).toHaveBeenCalledWith({
|
||||
sessionKey: 'agent:main:cron:job-water',
|
||||
limit: 200,
|
||||
});
|
||||
const timeline = useAcpChatSessionStore.getState().timeline;
|
||||
expect(timeline.itemOrder).toEqual(['cron-prompt:0', 'cron-result:0']);
|
||||
expect(timeline.itemsById['cron-prompt:0']).toMatchObject({ role: 'user' });
|
||||
expect(timeline.itemsById['cron-result:0']).toMatchObject({ role: 'assistant' });
|
||||
});
|
||||
|
||||
it('keeps non-empty ACP replay authoritative over cron fallback history', async () => {
|
||||
hostApiMock.loadAcpSession.mockResolvedValue({
|
||||
success: true,
|
||||
generation: 1,
|
||||
sessionUpdates: [{
|
||||
sessionKey: 'agent:main:cron:job-water',
|
||||
generation: 1,
|
||||
historical: true,
|
||||
notification: {
|
||||
sessionId: 'agent:main:cron:job-water',
|
||||
update: {
|
||||
sessionUpdate: 'agent_message_chunk',
|
||||
messageId: 'acp-result',
|
||||
content: { type: 'text', text: 'ACP authoritative result' },
|
||||
},
|
||||
},
|
||||
}],
|
||||
});
|
||||
hostApiMock.cronSessionHistory.mockResolvedValue({
|
||||
messages: [{ id: 'cron-result', role: 'assistant', content: 'Fallback result' }],
|
||||
});
|
||||
const { useAcpChatSessionStore } = await importStore();
|
||||
|
||||
await useAcpChatSessionStore.getState().loadSession({
|
||||
sessionKey: 'agent:main:cron:job-water',
|
||||
workspaceRoot: '/repo-a',
|
||||
cwd: '/repo-a',
|
||||
});
|
||||
|
||||
const timeline = useAcpChatSessionStore.getState().timeline;
|
||||
expect(timeline.itemOrder).toEqual(['acp-result:0']);
|
||||
});
|
||||
|
||||
it('prepares a local pending session by clearing renderer state without loading ACP', async () => {
|
||||
const { ensureAcpChatSubscriptions, useAcpChatSessionStore } = await importStore();
|
||||
ensureAcpChatSubscriptions();
|
||||
|
||||
@@ -68,3 +68,46 @@ describe('cron schedule normalization', () => {
|
||||
expect(update?.params.patch?.schedule).toEqual({ kind: 'at', at: '2031-02-03T10:30:00.000Z' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('cron session history', () => {
|
||||
it('reads SQLite-backed run summaries through cron.runs', async () => {
|
||||
const job = makeGatewayJob({ kind: 'cron', expr: '* * * * *' });
|
||||
const rpc = vi.fn(async (method: string) => {
|
||||
if (method === 'cron.list') return { jobs: [job] };
|
||||
if (method === 'cron.runs') {
|
||||
return {
|
||||
entries: [{
|
||||
jobId: 'job-1',
|
||||
status: 'ok',
|
||||
summary: 'Time to drink water.',
|
||||
sessionId: 'run-session-1',
|
||||
ts: 1_700_000_005_000,
|
||||
runAtMs: 1_700_000_000_000,
|
||||
durationMs: 5000,
|
||||
provider: 'provider-a',
|
||||
model: 'model-a',
|
||||
}],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
const api = createCronApi({ gatewayManager: { rpc } as unknown as GatewayManager });
|
||||
|
||||
const result = await api.sessionHistory({
|
||||
sessionKey: 'agent:main:cron:job-1',
|
||||
limit: 200,
|
||||
});
|
||||
|
||||
expect(rpc).toHaveBeenCalledWith('cron.runs', {
|
||||
id: 'job-1',
|
||||
limit: 200,
|
||||
sortDir: 'asc',
|
||||
}, 8000);
|
||||
expect(result).toMatchObject({
|
||||
messages: [
|
||||
{ role: 'user', content: 'hi' },
|
||||
{ role: 'assistant', content: expect.stringContaining('Time to drink water.') },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,11 +4,18 @@ import {
|
||||
getReconnectScheduleDecision,
|
||||
getReconnectSkipReason,
|
||||
isLifecycleSuperseded,
|
||||
isOpenClawFatalConfigExitCode,
|
||||
nextLifecycleEpoch,
|
||||
shouldDeferRestart,
|
||||
} from '@electron/gateway/process-policy';
|
||||
|
||||
describe('gateway process policy helpers', () => {
|
||||
it('recognizes OpenClaw EX_CONFIG without classifying ordinary exits as fatal config', () => {
|
||||
expect(isOpenClawFatalConfigExitCode(78)).toBe(true);
|
||||
expect(isOpenClawFatalConfigExitCode(1)).toBe(false);
|
||||
expect(isOpenClawFatalConfigExitCode(null)).toBe(false);
|
||||
});
|
||||
|
||||
describe('lifecycle epoch helpers', () => {
|
||||
it('increments lifecycle epoch by one', () => {
|
||||
expect(nextLifecycleEpoch(0)).toBe(1);
|
||||
|
||||
@@ -2,7 +2,9 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
connectGatewayWithStartupRetry,
|
||||
getGatewayStartupRecoveryAction,
|
||||
hasFatalRuntimeFailureSignal,
|
||||
hasInvalidConfigFailureSignal,
|
||||
hasStartupMigrationLockSignal,
|
||||
isGatewayStillStartingError,
|
||||
isInvalidConfigSignal,
|
||||
isTransientGatewayStartError,
|
||||
@@ -46,12 +48,34 @@ describe('gateway startup recovery heuristics', () => {
|
||||
expect(shouldAttemptConfigAutoRepair(new Error('start failed'), lines, true)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches common invalid-config phrases robustly', () => {
|
||||
it('matches common invalid-config and migration phrases robustly', () => {
|
||||
expect(isInvalidConfigSignal('Config invalid')).toBe(true);
|
||||
expect(isInvalidConfigSignal('skills: Unrecognized key: "enabled"')).toBe(true);
|
||||
expect(isInvalidConfigSignal('Startup migration blocked by legacy state')).toBe(true);
|
||||
expect(isInvalidConfigSignal(
|
||||
'OpenClaw startup migrations did not complete cleanly; refusing to report the gateway ready.',
|
||||
)).toBe(true);
|
||||
expect(isInvalidConfigSignal('Run: openclaw doctor --fix')).toBe(true);
|
||||
expect(isInvalidConfigSignal('Gateway ready after 3 attempts')).toBe(false);
|
||||
});
|
||||
|
||||
it('detects an active startup-migration lease that must not be restart-looped', () => {
|
||||
expect(hasStartupMigrationLockSignal(undefined, [
|
||||
'OpenClaw startup migrations are already running for this state directory; retry after the other gateway finishes or after 2026-07-21T03:02:57.489Z.',
|
||||
])).toBe(true);
|
||||
expect(hasStartupMigrationLockSignal(new Error('gateway start failed'), [])).toBe(false);
|
||||
});
|
||||
|
||||
it('detects Node and SQLite failures that cannot be repaired by doctor', () => {
|
||||
expect(hasFatalRuntimeFailureSignal(undefined, [
|
||||
'System Node 24.14.0 at /usr/bin/node uses SQLite 3.49.1, which is not WAL-reset-safe. Install Node 24.15+ (recommended) or Node 22.22.3+.',
|
||||
])).toBe(true);
|
||||
expect(hasFatalRuntimeFailureSignal(
|
||||
new Error('System Node 23.11.0 is outside the supported range'),
|
||||
[],
|
||||
)).toBe(true);
|
||||
expect(hasFatalRuntimeFailureSignal(new Error('Config invalid'), [])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGatewayStartupRecoveryAction', () => {
|
||||
@@ -69,7 +93,7 @@ describe('getGatewayStartupRecoveryAction', () => {
|
||||
expect(action).toBe('repair');
|
||||
});
|
||||
|
||||
it('returns retry when repair was attempted but error is still transient', () => {
|
||||
it('fails when the config error remains after the one doctor repair', () => {
|
||||
const action = getGatewayStartupRecoveryAction({
|
||||
startupError: transientError,
|
||||
startupStderrLines: configInvalidStderr,
|
||||
@@ -77,7 +101,7 @@ describe('getGatewayStartupRecoveryAction', () => {
|
||||
attempt: 1,
|
||||
maxAttempts: 3,
|
||||
});
|
||||
expect(action).toBe('retry');
|
||||
expect(action).toBe('fail');
|
||||
});
|
||||
|
||||
it('returns retry for transient errors after successful repair (no config signal)', () => {
|
||||
@@ -113,6 +137,33 @@ describe('getGatewayStartupRecoveryAction', () => {
|
||||
expect(action).toBe('fail');
|
||||
});
|
||||
|
||||
it('does not retry while an OpenClaw startup-migration lease is active', () => {
|
||||
const action = getGatewayStartupRecoveryAction({
|
||||
startupError: transientError,
|
||||
startupStderrLines: [
|
||||
'OpenClaw startup migrations are already running; retry after the other gateway finishes.',
|
||||
],
|
||||
configRepairAttempted: false,
|
||||
attempt: 1,
|
||||
maxAttempts: 3,
|
||||
});
|
||||
expect(action).toBe('fail');
|
||||
});
|
||||
|
||||
it('does not retry or run doctor for a fatal runtime incompatibility', () => {
|
||||
const action = getGatewayStartupRecoveryAction({
|
||||
startupError: new Error('Gateway process exited before becoming ready (code=1)'),
|
||||
startupStderrLines: [
|
||||
'System Node 24.14.0 uses SQLite 3.49.1, which is not WAL-reset-safe. Install Node 24.15+ or Node 22.22.3+.',
|
||||
'Run: openclaw doctor --fix',
|
||||
],
|
||||
configRepairAttempted: false,
|
||||
attempt: 1,
|
||||
maxAttempts: 3,
|
||||
});
|
||||
expect(action).toBe('fail');
|
||||
});
|
||||
|
||||
it('returns retry for gateway still starting handshake rejection', () => {
|
||||
const action = getGatewayStartupRecoveryAction({
|
||||
startupError: new Error('gateway starting; retry shortly'),
|
||||
|
||||
@@ -1,9 +1,74 @@
|
||||
// @vitest-environment node
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import { resolve } from 'node:path';
|
||||
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', () => {
|
||||
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',
|
||||
electron: '40.10.6',
|
||||
'@openclaw/discord': '2026.7.1',
|
||||
'@openclaw/qqbot': '2026.7.1',
|
||||
'@openclaw/whatsapp': '2026.7.1',
|
||||
'@soimy/dingtalk': '3.6.6',
|
||||
'@wecom/wecom-openclaw-plugin': '2026.7.2',
|
||||
'@larksuite/openclaw-lark': '2026.7.9',
|
||||
});
|
||||
|
||||
const nodeDownloadScript = readFileSync(
|
||||
resolve(process.cwd(), 'scripts/download-bundled-node.mjs'),
|
||||
'utf8',
|
||||
);
|
||||
expect(nodeDownloadScript).toContain("const NODE_VERSION = '22.22.3'");
|
||||
});
|
||||
|
||||
it('uses an Electron runtime with OpenClaw-compatible Node and SQLite versions', () => {
|
||||
const electronPath = require('electron') as string;
|
||||
const raw = execFileSync(
|
||||
electronPath,
|
||||
['-p', 'JSON.stringify({node:process.versions.node,sqlite:process.versions.sqlite})'],
|
||||
{
|
||||
encoding: 'utf8',
|
||||
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
|
||||
},
|
||||
);
|
||||
expect(JSON.parse(raw.trim())).toEqual({ node: '24.15.0', sqlite: '3.51.3' });
|
||||
});
|
||||
|
||||
it('keeps upstream WeCom and Open Lark manifest identities unchanged across the version bump', () => {
|
||||
const readManifest = (packagePath: string) => JSON.parse(readFileSync(
|
||||
resolve(process.cwd(), 'node_modules', ...packagePath.split('/'), 'openclaw.plugin.json'),
|
||||
'utf8',
|
||||
)) as { id?: string; channels?: string[] };
|
||||
|
||||
expect(readManifest('@wecom/wecom-openclaw-plugin')).toMatchObject({
|
||||
id: 'wecom-openclaw-plugin',
|
||||
channels: ['wecom'],
|
||||
});
|
||||
expect(readManifest('@larksuite/openclaw-lark')).toMatchObject({
|
||||
id: 'openclaw-lark',
|
||||
channels: ['feishu'],
|
||||
});
|
||||
});
|
||||
|
||||
it('does not retain superseded channel plugin packages in the lockfile', () => {
|
||||
const lockfile = readFileSync(resolve(process.cwd(), 'pnpm-lock.yaml'), 'utf8');
|
||||
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':");
|
||||
});
|
||||
|
||||
it('includes Electron runtime-only packages needed in packaged builds', async () => {
|
||||
const { ELECTRON_MAIN_RUNTIME_PACKAGES, EXTRA_BUNDLED_PACKAGES } = await import('../../scripts/openclaw-bundle-config.mjs');
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// @vitest-environment node
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { ensureOpenClaw2026_7_1UpgradeSnapshot } from '@electron/utils/openclaw-upgrade-snapshot';
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
async function createTempStateDir(): Promise<string> {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'clawx-openclaw-upgrade-'));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('OpenClaw 2026.7.1 upgrade snapshot', () => {
|
||||
it('copies config, SQLite sidecars, agent auth, and channel credentials once', async () => {
|
||||
const stateDir = await createTempStateDir();
|
||||
const configPath = join(stateDir, 'openclaw.json');
|
||||
await mkdir(join(stateDir, 'state'), { recursive: true });
|
||||
await mkdir(join(stateDir, 'agents', 'main', 'agent'), { recursive: true });
|
||||
await mkdir(join(stateDir, 'agents', 'main', 'sessions'), { recursive: true });
|
||||
await mkdir(join(stateDir, 'credentials', 'channel'), { recursive: true });
|
||||
await writeFile(configPath, '{"version":"old"}\n');
|
||||
await writeFile(join(stateDir, 'state', 'openclaw.sqlite'), 'db');
|
||||
await writeFile(join(stateDir, 'state', 'openclaw.sqlite-wal'), 'wal');
|
||||
await writeFile(join(stateDir, 'state', 'openclaw.sqlite-shm'), 'shm');
|
||||
await writeFile(join(stateDir, 'agents', 'main', 'agent', 'openclaw-agent.sqlite'), 'agent-db');
|
||||
await writeFile(join(stateDir, 'agents', 'main', 'agent', 'openclaw-agent.sqlite-wal'), 'agent-wal');
|
||||
await writeFile(join(stateDir, 'agents', 'main', 'agent', 'auth-profiles.json'), '{"profiles":{}}');
|
||||
await writeFile(join(stateDir, 'agents', 'main', 'sessions', 'history.jsonl'), 'large transcript');
|
||||
await writeFile(join(stateDir, 'credentials', 'channel', 'token.json'), '{"token":"secret"}');
|
||||
|
||||
const first = await ensureOpenClaw2026_7_1UpgradeSnapshot({ stateDir, configPath });
|
||||
expect(first.status).toBe('created');
|
||||
expect(first.files).toEqual(expect.arrayContaining([
|
||||
'config/openclaw.json',
|
||||
'state-files/state/openclaw.sqlite',
|
||||
'state-files/state/openclaw.sqlite-wal',
|
||||
'state-files/state/openclaw.sqlite-shm',
|
||||
'agents/main/agent/openclaw-agent.sqlite',
|
||||
'agents/main/agent/openclaw-agent.sqlite-wal',
|
||||
'agents/main/agent/auth-profiles.json',
|
||||
'credentials/channel/token.json',
|
||||
]));
|
||||
expect(first.files).not.toContain('agents/main/sessions/history.jsonl');
|
||||
|
||||
await writeFile(configPath, '{"version":"new"}\n');
|
||||
const second = await ensureOpenClaw2026_7_1UpgradeSnapshot({ stateDir, configPath });
|
||||
expect(second.status).toBe('exists');
|
||||
await expect(readFile(join(second.snapshotDir, 'config', 'openclaw.json'), 'utf8'))
|
||||
.resolves.toBe('{"version":"old"}\n');
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,7 @@ describe('plugin install index sqlite persistence', () => {
|
||||
|
||||
it('creates sqlite and upserts trusted whatsapp install records', async () => {
|
||||
const { upsertPluginInstallRecordsIntoSqlite } = await import('@electron/utils/plugin-install-index');
|
||||
const sqlitePath = join(stateDir, 'openclaw.sqlite');
|
||||
const sqlitePath = join(stateDir, 'state', 'openclaw.sqlite');
|
||||
const record = {
|
||||
source: 'npm',
|
||||
spec: '@openclaw/whatsapp',
|
||||
@@ -71,7 +71,7 @@ describe('plugin install index sqlite persistence', () => {
|
||||
|
||||
it('updates stale sqlite records when installPath changes', async () => {
|
||||
const { upsertPluginInstallRecordsIntoSqlite } = await import('@electron/utils/plugin-install-index');
|
||||
const sqlitePath = join(stateDir, 'openclaw.sqlite');
|
||||
const sqlitePath = join(stateDir, 'state', 'openclaw.sqlite');
|
||||
const stale = {
|
||||
source: 'npm',
|
||||
spec: '@openclaw/whatsapp',
|
||||
@@ -102,4 +102,35 @@ describe('plugin install index sqlite persistence', () => {
|
||||
const persisted = JSON.parse(row.install_records_json) as Record<string, Record<string, unknown>>;
|
||||
expect(persisted.whatsapp.installPath).toBe(fresh.installPath);
|
||||
});
|
||||
|
||||
it('removes compatibility-patched plugin records from canonical and legacy databases', async () => {
|
||||
const {
|
||||
removePluginInstallRecordsFromSqlite,
|
||||
upsertPluginInstallRecordsIntoSqlite,
|
||||
} = await import('@electron/utils/plugin-install-index');
|
||||
const sqlitePath = join(stateDir, 'state', 'openclaw.sqlite');
|
||||
const record = {
|
||||
source: 'npm',
|
||||
spec: '@wecom/wecom-openclaw-plugin',
|
||||
installPath: '/home/test/.openclaw/extensions/wecom',
|
||||
version: '2026.7.2',
|
||||
resolvedName: '@wecom/wecom-openclaw-plugin',
|
||||
resolvedVersion: '2026.7.2',
|
||||
resolvedSpec: '@wecom/wecom-openclaw-plugin@2026.7.2',
|
||||
};
|
||||
|
||||
expect(upsertPluginInstallRecordsIntoSqlite({ wecom: record })).toBe(true);
|
||||
expect(removePluginInstallRecordsFromSqlite(['wecom'])).toBe(true);
|
||||
|
||||
const { DatabaseSync } = await import('node:sqlite');
|
||||
const db = new DatabaseSync(sqlitePath);
|
||||
const row = db.prepare(`
|
||||
SELECT install_records_json
|
||||
FROM installed_plugin_index
|
||||
WHERE index_key = 'installed-plugin-index'
|
||||
`).get() as { install_records_json: string };
|
||||
db.close();
|
||||
|
||||
expect(JSON.parse(row.install_records_json)).not.toHaveProperty('wecom');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,14 +5,19 @@ const {
|
||||
mockCpSync,
|
||||
mockCopyFileSync,
|
||||
mockStatSync,
|
||||
mockLstatSync,
|
||||
mockMkdirSync,
|
||||
mockRmSync,
|
||||
mockSymlinkSync,
|
||||
mockUnlinkSync,
|
||||
mockReadFileSync,
|
||||
mockWriteFileSync,
|
||||
mockReaddirSync,
|
||||
mockRealpathSync,
|
||||
mockLoggerWarn,
|
||||
mockLoggerInfo,
|
||||
mockUpsertPluginInstallRecordsIntoSqlite,
|
||||
mockRemovePluginInstallRecordsFromSqlite,
|
||||
mockHomedir,
|
||||
mockApp,
|
||||
} = vi.hoisted(() => ({
|
||||
@@ -20,14 +25,19 @@ const {
|
||||
mockCpSync: vi.fn(),
|
||||
mockCopyFileSync: vi.fn(),
|
||||
mockStatSync: vi.fn(() => ({ isDirectory: () => false })),
|
||||
mockLstatSync: vi.fn(),
|
||||
mockMkdirSync: vi.fn(),
|
||||
mockRmSync: vi.fn(),
|
||||
mockSymlinkSync: vi.fn(),
|
||||
mockUnlinkSync: vi.fn(),
|
||||
mockReadFileSync: vi.fn(),
|
||||
mockWriteFileSync: vi.fn(),
|
||||
mockReaddirSync: vi.fn(),
|
||||
mockRealpathSync: vi.fn(),
|
||||
mockLoggerWarn: vi.fn(),
|
||||
mockLoggerInfo: vi.fn(),
|
||||
mockUpsertPluginInstallRecordsIntoSqlite: vi.fn(() => true),
|
||||
mockRemovePluginInstallRecordsFromSqlite: vi.fn(() => true),
|
||||
mockHomedir: vi.fn(() => '/home/test'),
|
||||
mockApp: {
|
||||
isPackaged: true,
|
||||
@@ -45,8 +55,11 @@ vi.mock('node:fs', async () => {
|
||||
cpSync: mockCpSync,
|
||||
copyFileSync: mockCopyFileSync,
|
||||
statSync: mockStatSync,
|
||||
lstatSync: mockLstatSync,
|
||||
mkdirSync: mockMkdirSync,
|
||||
rmSync: mockRmSync,
|
||||
symlinkSync: mockSymlinkSync,
|
||||
unlinkSync: mockUnlinkSync,
|
||||
readFileSync: mockReadFileSync,
|
||||
writeFileSync: mockWriteFileSync,
|
||||
readdirSync: mockReaddirSync,
|
||||
@@ -88,7 +101,8 @@ vi.mock('@electron/utils/logger', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('@electron/utils/plugin-install-index', () => ({
|
||||
upsertPluginInstallRecordsIntoSqlite: vi.fn(() => true),
|
||||
upsertPluginInstallRecordsIntoSqlite: mockUpsertPluginInstallRecordsIntoSqlite,
|
||||
removePluginInstallRecordsFromSqlite: mockRemovePluginInstallRecordsFromSqlite,
|
||||
ensureOpenClawStateDirExists: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -111,6 +125,13 @@ describe('plugin installer diagnostics', () => {
|
||||
mockCpSync.mockImplementation(() => undefined);
|
||||
mockMkdirSync.mockImplementation(() => undefined);
|
||||
mockRmSync.mockImplementation(() => undefined);
|
||||
mockSymlinkSync.mockImplementation(() => undefined);
|
||||
mockUnlinkSync.mockImplementation(() => undefined);
|
||||
mockLstatSync.mockImplementation(() => {
|
||||
const error = new Error('missing') as NodeJS.ErrnoException;
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
});
|
||||
mockReadFileSync.mockReturnValue('{}');
|
||||
mockWriteFileSync.mockImplementation(() => undefined);
|
||||
mockReaddirSync.mockReturnValue([]);
|
||||
@@ -123,6 +144,52 @@ describe('plugin installer diagnostics', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('adds the WeCom channel descriptor while preserving valid upstream npm metadata', async () => {
|
||||
const targetDir = '/home/test/.openclaw/extensions/wecom';
|
||||
mockExistsSync.mockImplementation((input: string) => [
|
||||
`${targetDir}/openclaw.plugin.json`,
|
||||
`${targetDir}/package.json`,
|
||||
`${targetDir}/dist/index.js`,
|
||||
].includes(String(input)));
|
||||
mockReadFileSync.mockImplementation((input: string) => {
|
||||
const value = String(input);
|
||||
if (value.endsWith('openclaw.plugin.json')) {
|
||||
return JSON.stringify({ id: 'wecom-openclaw-plugin', channels: ['wecom'] });
|
||||
}
|
||||
if (value.endsWith('package.json')) {
|
||||
return JSON.stringify({
|
||||
name: '@wecom/wecom',
|
||||
version: '2026.7.2',
|
||||
main: 'dist/index.js',
|
||||
openclaw: { install: { npmSpec: '@wecom/wecom', localPath: 'extensions/wecom' } },
|
||||
});
|
||||
}
|
||||
if (value.endsWith('dist/index.js')) {
|
||||
return 'export default { id: "wecom-openclaw-plugin" };';
|
||||
}
|
||||
return '{}';
|
||||
});
|
||||
|
||||
const { fixupPluginManifest } = await import('@electron/utils/plugin-install');
|
||||
fixupPluginManifest(targetDir);
|
||||
|
||||
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
||||
`${targetDir}/openclaw.plugin.json`,
|
||||
expect.stringContaining('"channelConfigs"'),
|
||||
'utf-8',
|
||||
);
|
||||
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
||||
`${targetDir}/openclaw.plugin.json`,
|
||||
expect.stringContaining('"id": "wecom"'),
|
||||
'utf-8',
|
||||
);
|
||||
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
||||
`${targetDir}/package.json`,
|
||||
expect.stringContaining('"name": "@wecom/wecom-openclaw-plugin"'),
|
||||
'utf-8',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns source-missing warning when bundled mirror cannot be found', async () => {
|
||||
const { ensurePluginInstalled } = await import('@electron/utils/plugin-install');
|
||||
const result = ensurePluginInstalled('wecom', ['/bundle/wecom'], 'WeCom');
|
||||
@@ -223,7 +290,7 @@ describe('plugin installer diagnostics', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('writes trusted install metadata for mirrored official whatsapp plugin', async () => {
|
||||
it('writes trusted SQLite metadata for mirrored official whatsapp plugin', async () => {
|
||||
const configPath = '/home/test/.openclaw/openclaw.json';
|
||||
const targetDir = '/home/test/.openclaw/extensions/whatsapp';
|
||||
const sourceDir = '/bundle/whatsapp';
|
||||
@@ -255,15 +322,154 @@ describe('plugin installer diagnostics', () => {
|
||||
const result = ensurePluginInstalled('whatsapp', [sourceDir], 'WhatsApp');
|
||||
|
||||
expect(result.installed).toBe(true);
|
||||
expect(mockUpsertPluginInstallRecordsIntoSqlite).toHaveBeenCalledWith({
|
||||
whatsapp: expect.objectContaining({
|
||||
installPath: targetDir,
|
||||
resolvedName: '@openclaw/whatsapp',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('removes WeCom updater metadata for the patched legacy-compatible plugin id', async () => {
|
||||
const configPath = '/home/test/.openclaw/openclaw.json';
|
||||
const targetDir = '/home/test/.openclaw/extensions/wecom';
|
||||
|
||||
mockExistsSync.mockImplementation((input: string) => {
|
||||
const value = String(input);
|
||||
return value === configPath
|
||||
|| value === `${targetDir}/openclaw.plugin.json`
|
||||
|| value === `${targetDir}/package.json`;
|
||||
});
|
||||
mockReadFileSync.mockImplementation((input: string) => {
|
||||
if (String(input) === configPath) {
|
||||
return JSON.stringify({
|
||||
plugins: {
|
||||
installs: {
|
||||
wecom: {
|
||||
source: 'npm',
|
||||
spec: '@wecom/wecom-openclaw-plugin',
|
||||
version: '2026.6.23',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (String(input) === `${targetDir}/package.json`) {
|
||||
return JSON.stringify({ version: '2026.7.2' });
|
||||
}
|
||||
return '{}';
|
||||
});
|
||||
mockRealpathSync.mockImplementation((input: string) => input);
|
||||
|
||||
const { syncTrustedOfficialPluginInstallRecord } = await import('@electron/utils/plugin-install');
|
||||
expect(syncTrustedOfficialPluginInstallRecord('wecom', targetDir)).toBe(true);
|
||||
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
||||
configPath,
|
||||
expect.stringContaining(`"installPath": "${targetDir}"`),
|
||||
expect.not.stringContaining('"installs"'),
|
||||
'utf-8',
|
||||
);
|
||||
expect(mockRemovePluginInstallRecordsFromSqlite).toHaveBeenCalledWith(['wecom-openclaw-plugin']);
|
||||
expect(mockUpsertPluginInstallRecordsIntoSqlite).toHaveBeenCalledWith({
|
||||
wecom: expect.objectContaining({
|
||||
source: 'path',
|
||||
sourcePath: targetDir,
|
||||
installPath: targetDir,
|
||||
version: '2026.7.2',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('replaces legacy Feishu npm ownership with the ClawX path mirror', async () => {
|
||||
const configPath = '/home/test/.openclaw/openclaw.json';
|
||||
const targetDir = '/home/test/.openclaw/extensions/feishu-openclaw-plugin';
|
||||
|
||||
mockExistsSync.mockImplementation((input: string) => {
|
||||
const value = String(input);
|
||||
return value === configPath
|
||||
|| value === `${targetDir}/openclaw.plugin.json`
|
||||
|| value === `${targetDir}/package.json`;
|
||||
});
|
||||
mockReadFileSync.mockImplementation((input: string) => {
|
||||
if (String(input) === configPath) {
|
||||
return JSON.stringify({
|
||||
plugins: {
|
||||
installs: {
|
||||
'openclaw-lark': { source: 'npm', version: '2026.6.10' },
|
||||
'feishu-openclaw-plugin': { source: 'npm', version: '2026.6.10' },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (String(input) === `${targetDir}/package.json`) {
|
||||
return JSON.stringify({ version: '2026.7.9' });
|
||||
}
|
||||
return '{}';
|
||||
});
|
||||
|
||||
const { syncTrustedOfficialPluginInstallRecord } = await import('@electron/utils/plugin-install');
|
||||
expect(syncTrustedOfficialPluginInstallRecord('feishu-openclaw-plugin', targetDir)).toBe(true);
|
||||
|
||||
expect(mockWriteFileSync).toHaveBeenCalledWith(
|
||||
configPath,
|
||||
expect.stringContaining('"resolvedName": "@openclaw/whatsapp"'),
|
||||
expect.not.stringContaining('"installs"'),
|
||||
'utf-8',
|
||||
);
|
||||
expect(mockRemovePluginInstallRecordsFromSqlite).toHaveBeenCalledWith([
|
||||
'feishu-openclaw-plugin',
|
||||
'feishu',
|
||||
]);
|
||||
expect(mockUpsertPluginInstallRecordsIntoSqlite).toHaveBeenCalledWith({
|
||||
'openclaw-lark': expect.objectContaining({
|
||||
source: 'path',
|
||||
sourcePath: targetDir,
|
||||
installPath: targetDir,
|
||||
version: '2026.7.9',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('removes stale metadata even when an unconfigured mirror directory is already missing', async () => {
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
|
||||
const { removeTrustedOfficialPluginInstallRecord } = await import('@electron/utils/plugin-install');
|
||||
expect(removeTrustedOfficialPluginInstallRecord('whatsapp')).toBe(true);
|
||||
expect(mockRemovePluginInstallRecordsFromSqlite).toHaveBeenCalledWith(['whatsapp']);
|
||||
});
|
||||
|
||||
it('links a mirrored plugin openclaw peer to the bundled runtime', async () => {
|
||||
const targetDir = '/home/test/.openclaw/extensions/qqbot';
|
||||
const openclawDir = '/app/resources/openclaw';
|
||||
const nodeModulesDir = `${targetDir}/node_modules`;
|
||||
const linkPath = `${nodeModulesDir}/openclaw`;
|
||||
let linked = false;
|
||||
|
||||
mockExistsSync.mockImplementation((input: string) => String(input) === `${openclawDir}/package.json`);
|
||||
mockReadFileSync.mockImplementation((input: string) => {
|
||||
if (String(input) === `${targetDir}/package.json`) {
|
||||
return JSON.stringify({ peerDependencies: { openclaw: '>=2026.7.1' } });
|
||||
}
|
||||
return '{}';
|
||||
});
|
||||
mockLstatSync.mockImplementation((input: string) => {
|
||||
if (String(input) === nodeModulesDir) {
|
||||
return {
|
||||
isDirectory: () => true,
|
||||
isSymbolicLink: () => false,
|
||||
};
|
||||
}
|
||||
const error = new Error('missing') as NodeJS.ErrnoException;
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
});
|
||||
mockSymlinkSync.mockImplementation(() => {
|
||||
linked = true;
|
||||
});
|
||||
mockRealpathSync.mockImplementation((input: string) => (
|
||||
linked && String(input) === linkPath ? openclawDir : String(input)
|
||||
));
|
||||
|
||||
const { repairPluginOpenClawPeerLink } = await import('@electron/utils/plugin-install');
|
||||
expect(repairPluginOpenClawPeerLink(targetDir, openclawDir)).toBe(true);
|
||||
expect(mockSymlinkSync).toHaveBeenCalledWith(openclawDir, linkPath, 'junction');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user