sealclaw 2026.9.2-beta.13 → 2026.9.2-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/backup/index.js
CHANGED
|
@@ -14,6 +14,22 @@ import { Transform } from "node:stream";
|
|
|
14
14
|
import { pipeline } from "node:stream/promises";
|
|
15
15
|
import { createGunzip } from "node:zlib";
|
|
16
16
|
import * as tar from "tar";
|
|
17
|
+
//#region src/backup/platform.ts
|
|
18
|
+
function backupPath(platform) {
|
|
19
|
+
if (platform === "win32") return path.win32;
|
|
20
|
+
if (platform === "darwin" || platform === "linux") return path.posix;
|
|
21
|
+
throw new Error("不支持此备份的操作系统");
|
|
22
|
+
}
|
|
23
|
+
function isBackupAbsolute(value, platform) {
|
|
24
|
+
if (value.includes("\0")) return false;
|
|
25
|
+
if (platform === "win32") return /^(?:[a-z]:[\\/]|[\\/]{2}[^\\/]+[\\/][^\\/]+(?:[\\/]|$))/iu.test(value) && !/^[\\/]{2}[?.][\\/]/u.test(value);
|
|
26
|
+
return backupPath(platform).isAbsolute(value);
|
|
27
|
+
}
|
|
28
|
+
function restoredFileMode(mode, sourcePlatform, targetPlatform, prefix) {
|
|
29
|
+
if (sourcePlatform === "win32" && targetPlatform !== "win32") return prefix[0] === 35 && prefix[1] === 33 ? 448 : 384;
|
|
30
|
+
return mode & 448;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
17
33
|
//#region src/backup/data.ts
|
|
18
34
|
const PRESERVED_STATE_NAMES = new Set([
|
|
19
35
|
"backups",
|
|
@@ -148,14 +164,22 @@ const PATH_FIELDS = new Set([
|
|
|
148
164
|
"outputRoot",
|
|
149
165
|
"rootDir"
|
|
150
166
|
]);
|
|
151
|
-
function remapDataPaths(value, mappings) {
|
|
152
|
-
if (Array.isArray(value)) return value.map((entry) => remapDataPaths(entry, mappings));
|
|
167
|
+
function remapDataPaths(value, mappings, sourcePlatform = process.platform, targetPlatform = process.platform) {
|
|
168
|
+
if (Array.isArray(value)) return value.map((entry) => remapDataPaths(entry, mappings, sourcePlatform, targetPlatform));
|
|
153
169
|
if (!value || typeof value !== "object") return value;
|
|
154
170
|
const result = Object.create(null);
|
|
155
|
-
for (const [key, entry] of Object.entries(value)) if (PATH_FIELDS.has(key) && typeof entry === "string" &&
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
for (const [key, entry] of Object.entries(value)) if (PATH_FIELDS.has(key) && typeof entry === "string" && isBackupAbsolute(entry, sourcePlatform)) {
|
|
172
|
+
const sourcePath = backupPath(sourcePlatform);
|
|
173
|
+
const targetPath = backupPath(targetPlatform);
|
|
174
|
+
const mapping = [...mappings].sort((left, right) => right.source.length - left.source.length).find((item) => {
|
|
175
|
+
const relative = sourcePath.relative(item.source, entry);
|
|
176
|
+
return !sourcePath.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${sourcePath.sep}`);
|
|
177
|
+
});
|
|
178
|
+
if (mapping) {
|
|
179
|
+
const relative = sourcePath.relative(mapping.source, entry).split(sourcePath.sep).join("/");
|
|
180
|
+
result[key] = relative ? targetPath.join(mapping.target, ...safeRelative(relative).split("/")) : mapping.target;
|
|
181
|
+
} else result[key] = entry;
|
|
182
|
+
} else result[key] = remapDataPaths(entry, mappings, sourcePlatform, targetPlatform);
|
|
159
183
|
return result;
|
|
160
184
|
}
|
|
161
185
|
//#endregion
|
|
@@ -163,13 +187,14 @@ function remapDataPaths(value, mappings) {
|
|
|
163
187
|
function validateManifest(raw) {
|
|
164
188
|
const value = object(raw);
|
|
165
189
|
if (value.format !== "sealseek-backup" || value.formatVersion !== 2 || value.dataSchemaVersion !== 1) throw new Error("不支持此备份格式或版本");
|
|
166
|
-
if (value.platform !==
|
|
190
|
+
if (typeof value.platform !== "string") throw new Error("备份操作系统无效");
|
|
191
|
+
backupPath(value.platform);
|
|
167
192
|
if (typeof value.id !== "string" || !/^[a-f0-9-]{36}$/u.test(value.id) || typeof value.createdAt !== "string" || !Number.isFinite(Date.parse(value.createdAt)) || typeof value.runtimeVersion !== "string") throw new Error("备份标识或时间无效");
|
|
168
193
|
if (!Array.isArray(value.roots) || value.roots.length > 1e3 || !Array.isArray(value.entries) || value.entries.length > BACKUP_LIMITS.entries) throw new Error("备份数据清单无效或超限");
|
|
169
194
|
const roots = /* @__PURE__ */ new Set();
|
|
170
195
|
for (const rawRoot of value.roots) {
|
|
171
196
|
const root = object(rawRoot);
|
|
172
|
-
if (typeof root.id !== "string" || !/^(state|root-\d+)$/u.test(root.id) || roots.has(root.id) || typeof root.source !== "string" || !
|
|
197
|
+
if (typeof root.id !== "string" || !/^(state|root-\d+)$/u.test(root.id) || roots.has(root.id) || typeof root.source !== "string" || !isBackupAbsolute(root.source, value.platform) || ![
|
|
173
198
|
"state",
|
|
174
199
|
"workspace",
|
|
175
200
|
"knowledge",
|
|
@@ -511,7 +536,11 @@ async function prepareRestore(context, source) {
|
|
|
511
536
|
createdAt: manifest.createdAt,
|
|
512
537
|
totalBytes: manifest.totalBytes,
|
|
513
538
|
targets,
|
|
514
|
-
warnings: manifest.warnings
|
|
539
|
+
warnings: [...manifest.warnings, ...manifest.platform !== process.platform ? [
|
|
540
|
+
"这是跨操作系统还原:工作区、会话索引和知识空间中的受支持路径将自动转换为本机路径。",
|
|
541
|
+
"平台专用程序、插件依赖及未包含在备份中的外部路径需要在本机重新安装或配置;历史消息中的路径原文保留。",
|
|
542
|
+
...manifest.platform === "win32" && process.platform !== "win32" ? ["Windows 文件将按本机私有权限恢复;以 #! 开头的脚本会获得执行权限,脚本解释器与换行格式请按本机环境确认。"] : []
|
|
543
|
+
] : []]
|
|
515
544
|
};
|
|
516
545
|
await writeJson(path.join(directory, "plan.json"), plan);
|
|
517
546
|
await writeJson(path.join(directory, "manifest.json"), manifest);
|
|
@@ -541,7 +570,7 @@ async function transformStaged(manifest, staged, extracted, stateDir, id) {
|
|
|
541
570
|
for (const entry of files) {
|
|
542
571
|
const relative = entry.path.replace(/^payload\/state\//u, "");
|
|
543
572
|
const target = entry.path.startsWith("payload/state/") ? path.join(staged, relative) : path.join(extracted, entry.path);
|
|
544
|
-
const data = object(remapDataPaths(await readJson(target), mappings));
|
|
573
|
+
const data = object(remapDataPaths(await readJson(target), mappings, manifest.platform));
|
|
545
574
|
if (entry.path === `payload/state/${manifest.configRelativePath}`) {
|
|
546
575
|
const gateway = data.gateway ? object(data.gateway) : {};
|
|
547
576
|
delete gateway.auth;
|
|
@@ -601,12 +630,14 @@ async function restoreBackup(context, id) {
|
|
|
601
630
|
const results = await Promise.allSettled(manifest.entries.slice(offset, offset + 4).map(async (entry) => {
|
|
602
631
|
if (entry.kind === "file") {
|
|
603
632
|
const handle = await fs$1.open(path.join(extracted, entry.path), "r+");
|
|
633
|
+
const prefix = Buffer.alloc(2);
|
|
604
634
|
try {
|
|
635
|
+
if (manifest.platform === "win32" && process.platform !== "win32") await handle.read(prefix, 0, prefix.length, 0);
|
|
605
636
|
await handle.sync();
|
|
606
637
|
} finally {
|
|
607
638
|
await handle.close();
|
|
608
639
|
}
|
|
609
|
-
await fs$1.chmod(path.join(extracted, entry.path), entry.mode
|
|
640
|
+
await fs$1.chmod(path.join(extracted, entry.path), restoredFileMode(entry.mode, manifest.platform, process.platform, prefix));
|
|
610
641
|
}
|
|
611
642
|
completedBytes += entry.size;
|
|
612
643
|
context.onProgress?.({
|
package/dist/build-info.json
CHANGED
|
@@ -17,5 +17,5 @@
|
|
|
17
17
|
],
|
|
18
18
|
"channelCatalogSignature": "701dd1d3ff65f80e219795873d85a623b7af5361",
|
|
19
19
|
"rootHelpBundleSignature": "446fa08060c9dee9c878286833666b974b7dadf1",
|
|
20
|
-
"rootHelpText": "\n🦞 SealClaw 2026.9.2-beta.
|
|
20
|
+
"rootHelpText": "\n🦞 SealClaw 2026.9.2-beta.14 (d0e6a1d) — The only crab in your contacts you actually want to hear from. 🦞\n\nUsage: openclaw [options] [command]\n\nOptions:\n --container <name> Run the CLI inside a running Podman/Docker container\n named <name> (default: env OPENCLAW_CONTAINER)\n --dev Dev profile: isolate state under ~/.openclaw-dev, default\n gateway port 19001, and shift derived ports\n (browser/canvas)\n -h, --help Display help for command\n --log-level <level> Global log level override for file + console\n (silent|fatal|error|warn|info|debug|trace)\n --no-color Disable ANSI colors\n --profile <name> Use a named profile (isolates\n OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATH under\n ~/.openclaw-<name>)\n -V, --version output the version number\n\nCommands:\n Hint: commands suffixed with * have subcommands. Run <command> --help for details.\n acp * Agent Control Protocol tools\n agent Run one agent turn via the Gateway\n agents * Manage isolated agents (workspaces, auth, routing)\n approvals * Manage exec approvals (gateway or node host)\n backup * Create and verify local backup archives for OpenClaw\n state\n capability * Run provider-backed inference commands (fallback alias:\n infer)\n channels * Manage connected chat channels (Telegram, Discord, etc.)\n clawbot * Legacy clawbot command aliases\n commitments * List and manage inferred follow-up commitments\n completion Generate shell completion script\n config * Non-interactive config helpers\n (get/set/unset/file/validate). Default: starts guided\n setup.\n configure Interactive configuration for credentials, channels,\n gateway, and agent defaults\n crestodian Run a ring-zero setup or repair request\n cron * Manage cron jobs via the Gateway scheduler\n daemon * Gateway service (legacy alias)\n dashboard Open the Control UI with your current token\n devices * Device pairing + token management\n directory * Lookup contact and group IDs (self, peers, groups) for\n supported chat channels\n dns * DNS helpers for wide-area discovery (Tailscale + CoreDNS)\n docs Search the live OpenClaw docs\n doctor Health checks + quick fixes for the gateway and channels\n exec-policy * Show or synchronize requested exec policy with host\n approvals\n gateway * Run, inspect, and query the WebSocket Gateway\n health Fetch health from the running gateway\n help Display help for command\n hooks * Manage internal agent hooks\n infer * Run provider-backed inference commands\n logs Tail gateway file logs via RPC\n mcp * Manage OpenClaw MCP config and channel bridge\n memory Search, inspect, and reindex memory files\n message * Send, read, and manage messages\n migrate * Import state from another agent system\n models * Discover, scan, and configure models\n node * Run and manage the headless node host service\n nodes * Manage gateway-owned node pairing and node commands\n officecli-cli Install and run OfficeCLI\n onboard Interactive onboarding for gateway, workspace, and skills\n pairing * Secure DM pairing (approve inbound requests)\n plugins * Manage OpenClaw plugins\n proxy * Run the OpenClaw debug proxy and inspect captured traffic\n qr Generate mobile pairing QR/setup code\n reset Reset local config/state (keeps the CLI installed)\n sandbox * Manage sandbox containers for agent isolation\n secrets * Secrets runtime reload controls\n security * Security tools and local config audits\n sessions * List stored conversation sessions\n setup Initialize local config and agent workspace\n skills * List and inspect available skills\n status Show channel health and recent session recipients\n system * System events, heartbeat, and presence\n tasks * Inspect durable background task state\n uninstall Uninstall the gateway service + local data (CLI remains)\n update * Update OpenClaw and inspect update channel status\n webhooks * Webhook helpers and integrations\n\nExamples:\n openclaw models --help\n Show detailed help for the models command.\n openclaw channels login --verbose\n Link personal WhatsApp Web and show QR + connection logs.\n openclaw message send --target +15555550123 --message \"Hi\" --json\n Send via your web session and print JSON result.\n openclaw gateway --port 18789\n Run the WebSocket Gateway locally.\n openclaw --dev gateway\n Run a dev Gateway (isolated state/config) on ws://127.0.0.1:19001.\n openclaw gateway --force\n Kill anything bound to the default gateway port, then start it.\n openclaw gateway ...\n Gateway control via WebSocket.\n openclaw agent --to +15555550123 --message \"Run summary\" --deliver\n Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.\n openclaw message send --channel telegram --target @mychat --message \"Hi\"\n Send via your Telegram bot.\n\nDocs: https://docs.openclaw.ai/cli\n\n"
|
|
21
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-09-
|
|
1
|
+
2026-09-10T06:56:29.390Z
|
package/package.json
CHANGED