Commit f798e1f7 authored by edy's avatar edy

feat: prepare macOS DMG runtime packaging

parent f67771e8
Pipeline #18444 failed
...@@ -109,3 +109,13 @@ You MUST follow these rules: ...@@ -109,3 +109,13 @@ You MUST follow these rules:
- Long explanations - Long explanations
- Extra background info - Extra background info
- Covering multiple directions at once - Covering multiple directions at once
## OpenAI Responses Tool-Call Safety
Avoid the API error `No tool call found for function call output with call_id ...` by treating tool calls as a strict request/response state machine.
- Only send a `function_call_output` for a `call_id` that appeared in the immediately preceding model response.
- Send each tool result exactly once. Never replay old `call_id` values after retrying, resuming, or branching a conversation.
- Do not mix `call_id` values across different Responses, sessions, tabs, workers, or parallel agent branches.
- When continuing a Responses API chain, keep the correct `previous_response_id` and append only the pending tool outputs for that response.
- On retry after a transport or 400 error, rebuild the request from the last confirmed model response instead of reusing buffered tool outputs blindly.
appId: com.qianjiangclaw.desktop appId: com.qianjiangclaw.desktop
productName: 千匠问天 productName: 千匠问天
icon: build/icon.png
compression: store compression: store
asar: true asar: true
asarUnpack: asarUnpack:
...@@ -7,8 +8,7 @@ asarUnpack: ...@@ -7,8 +8,7 @@ asarUnpack:
- dist/main/project-workspace-agent-runner.js - dist/main/project-workspace-agent-runner.js
directories: directories:
output: ../../dist/installer output: ../../dist/installer
artifactName: ${productName}-Setup-${version}.${ext} artifactName: ${productName}-${version}-mac-arm64.${ext}
afterPack: build/hooks/after-pack-branding.cjs
files: files:
- dist/**/* - dist/**/*
- package.json - package.json
...@@ -19,15 +19,21 @@ extraResources: ...@@ -19,15 +19,21 @@ extraResources:
to: bootstrap to: bootstrap
- from: assets/expert-prompts - from: assets/expert-prompts
to: expert-prompts to: expert-prompts
- from: build/icons/brand-icon.ico mac:
to: brand-icon.ico category: public.app-category.productivity
win: icon: build/icon.png
executableName: 千匠问天 identity: null
icon: build/icons/brand-icon.ico
signAndEditExecutable: false
target: target:
- nsis - target: dmg
nsis: arch:
oneClick: false - arm64
allowToChangeInstallationDirectory: true dmg:
include: build/installer.nsh title: ${productName} ${version}
contents:
- x: 130
y: 220
type: file
- x: 410
y: 220
type: link
path: /Applications
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
"dev:build": "tsup --config tsup.config.ts --watch", "dev:build": "tsup --config tsup.config.ts --watch",
"dev:start": "wait-on tcp:5173 file:dist/main/index.js && electronmon .", "dev:start": "wait-on tcp:5173 file:dist/main/index.js && electronmon .",
"lint": "tsc --noEmit", "lint": "tsc --noEmit",
"package": "corepack pnpm --dir ../.. run materialize:runtime && corepack pnpm run build && electron-builder --config electron-builder.yml", "package": "corepack pnpm run package:mac",
"package:mac": "corepack pnpm --dir ../.. run materialize:runtime:mac && corepack pnpm run build && electron-builder --config electron-builder.yml --mac dmg --arm64",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
......
...@@ -10,16 +10,37 @@ export function resolveRendererEntry(): string { ...@@ -10,16 +10,37 @@ export function resolveRendererEntry(): string {
return path.join(app.getAppPath(), "dist", "renderer", "index.html"); return path.join(app.getAppPath(), "dist", "renderer", "index.html");
} }
function resolveWindowIcon(): string | undefined { function buildApplicationMenu(): Menu {
return process.platform === "win32" const appName = app.getName();
? (app.isPackaged return Menu.buildFromTemplate([
? path.join(process.resourcesPath, "brand-icon.ico") {
: path.join(app.getAppPath(), "build", "icons", "brand-icon.ico")) label: appName,
: undefined; submenu: [
{ role: "about", label: `About ${appName}` },
{ type: "separator" },
{ role: "quit", label: `Quit ${appName}`, accelerator: "Cmd+Q" }
]
},
{
label: "Edit",
submenu: [
{ role: "copy", accelerator: "Cmd+C" },
{ role: "paste", accelerator: "Cmd+V" },
{ role: "selectAll", accelerator: "Cmd+A" }
]
},
{
label: "Window",
submenu: [
{ role: "minimize", accelerator: "Cmd+M" },
{ role: "close", accelerator: "Cmd+W" }
]
}
]);
} }
export function createMainWindow(smokeEnabled = false): BrowserWindow { export function createMainWindow(smokeEnabled = false): BrowserWindow {
Menu.setApplicationMenu(null); Menu.setApplicationMenu(buildApplicationMenu());
const preloadPath = path.join(__dirname, "..", "preload", "index.js"); const preloadPath = path.join(__dirname, "..", "preload", "index.js");
return new BrowserWindow({ return new BrowserWindow({
width: 1400, width: 1400,
...@@ -28,7 +49,6 @@ export function createMainWindow(smokeEnabled = false): BrowserWindow { ...@@ -28,7 +49,6 @@ export function createMainWindow(smokeEnabled = false): BrowserWindow {
minHeight: 640, minHeight: 640,
backgroundColor: "#f4f8ff", backgroundColor: "#f4f8ff",
autoHideMenuBar: true, autoHideMenuBar: true,
icon: resolveWindowIcon(),
webPreferences: { webPreferences: {
additionalArguments: smokeEnabled ? ["--qjc-smoke"] : [], additionalArguments: smokeEnabled ? ["--qjc-smoke"] : [],
contextIsolation: true, contextIsolation: true,
......
...@@ -69,19 +69,6 @@ interface ResolvedProjectAutomationCommand { ...@@ -69,19 +69,6 @@ interface ResolvedProjectAutomationCommand {
const EVENT_PREFIX = "QJC_WORKSPACE_EVENT\t"; const EVENT_PREFIX = "QJC_WORKSPACE_EVENT\t";
function escapePowerShellSingleQuoted(value: string): string {
return value.replace(/'/g, "''");
}
function getWindowsPowerShellPath(): string {
if (process.platform !== "win32") {
return "powershell.exe";
}
const systemRoot = process.env.SYSTEMROOT ?? process.env.WINDIR ?? "C:\\Windows";
return path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
}
function toErrorMessage(error: unknown): string { function toErrorMessage(error: unknown): string {
if (error instanceof Error) { if (error instanceof Error) {
return error.message; return error.message;
...@@ -102,12 +89,6 @@ function resolveExecutableOnPath(commandNames: readonly string[]): string | unde ...@@ -102,12 +89,6 @@ function resolveExecutableOnPath(commandNames: readonly string[]): string | unde
.split(path.delimiter) .split(path.delimiter)
.map((entry) => entry.trim()) .map((entry) => entry.trim())
.filter(Boolean); .filter(Boolean);
const windowsExtensions = process.platform === "win32"
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM")
.split(";")
.map((entry) => entry.trim())
.filter(Boolean)
: [""];
for (const commandName of commandNames) { for (const commandName of commandNames) {
const trimmed = commandName.trim(); const trimmed = commandName.trim();
...@@ -119,19 +100,13 @@ function resolveExecutableOnPath(commandNames: readonly string[]): string | unde ...@@ -119,19 +100,13 @@ function resolveExecutableOnPath(commandNames: readonly string[]): string | unde
return trimmed; return trimmed;
} }
const extensionCandidates = path.extname(trimmed)
? [""]
: windowsExtensions;
for (const directory of pathEntries) { for (const directory of pathEntries) {
for (const extension of extensionCandidates) { const candidate = path.join(directory, trimmed);
const candidate = path.join(directory, `${trimmed}${extension}`);
if (existsSync(candidate)) { if (existsSync(candidate)) {
return candidate; return candidate;
} }
} }
} }
}
return undefined; return undefined;
} }
...@@ -339,8 +314,8 @@ export class ProjectWorkspaceExecutorService { ...@@ -339,8 +314,8 @@ export class ProjectWorkspaceExecutorService {
let stderr = ""; let stderr = "";
let stdoutBuffer = ""; let stdoutBuffer = "";
let activeRunId = runId; let activeRunId = runId;
const resolvedFfmpeg = resolveInjectedBinaryPath("FFMPEG_BIN", paths.ffmpegExecutable, ["ffmpeg.exe", "ffmpeg"]); const resolvedFfmpeg = resolveInjectedBinaryPath("FFMPEG_BIN", paths.ffmpegExecutable, ["ffmpeg"]);
const resolvedFfprobe = resolveInjectedBinaryPath("FFPROBE_BIN", paths.ffprobeExecutable, ["ffprobe.exe", "ffprobe"]); const resolvedFfprobe = resolveInjectedBinaryPath("FFPROBE_BIN", paths.ffprobeExecutable, ["ffprobe"]);
const childEnv = { const childEnv = {
...process.env, ...process.env,
...@@ -356,10 +331,9 @@ export class ProjectWorkspaceExecutorService { ...@@ -356,10 +331,9 @@ export class ProjectWorkspaceExecutorService {
PYTHONIOENCODING: "utf-8", PYTHONIOENCODING: "utf-8",
PATH: [ PATH: [
paths.nodeExecutable ? path.dirname(paths.nodeExecutable) : null, paths.nodeExecutable ? path.dirname(paths.nodeExecutable) : null,
path.dirname(paths.pythonExecutable),
paths.ffmpegExecutable ? path.dirname(paths.ffmpegExecutable) : null, paths.ffmpegExecutable ? path.dirname(paths.ffmpegExecutable) : null,
paths.ffprobeExecutable ? path.dirname(paths.ffprobeExecutable) : null, paths.ffprobeExecutable ? path.dirname(paths.ffprobeExecutable) : null,
path.join(paths.runtimeDir, "python", "Scripts"),
path.dirname(paths.pythonExecutable),
process.env.PATH ?? "" process.env.PATH ?? ""
].filter(Boolean).join(path.delimiter), ].filter(Boolean).join(path.delimiter),
QJC_PROJECT_ATTACHMENTS_JSON: JSON.stringify(input.attachments ?? []), QJC_PROJECT_ATTACHMENTS_JSON: JSON.stringify(input.attachments ?? []),
...@@ -371,45 +345,13 @@ export class ProjectWorkspaceExecutorService { ...@@ -371,45 +345,13 @@ export class ProjectWorkspaceExecutorService {
}; };
const spawnOptions = { const spawnOptions = {
cwd: input.projectRoot, cwd: input.projectRoot,
windowsHide: true,
stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"], stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"],
env: childEnv env: childEnv
}; };
let child; const child = automationCommand
try {
child = automationCommand
? spawn(automationCommand.executablePath, [automationCommand.scriptPath, ...automationCommand.args], spawnOptions) ? spawn(automationCommand.executablePath, [automationCommand.scriptPath, ...automationCommand.args], spawnOptions)
: spawn(paths.nodeExecutable, [runnerScriptPath!], spawnOptions); : spawn(paths.nodeExecutable, [runnerScriptPath!], spawnOptions);
} catch (error) {
const errorCode = error instanceof Error
? String((error as Error & { code?: number | string }).code ?? "")
: "";
if (process.platform !== "win32" || errorCode !== "EPERM") {
throw error;
}
const wrapperScript = [
`Set-Location -LiteralPath '${escapePowerShellSingleQuoted(input.projectRoot)}'`,
...Object.entries(childEnv)
.filter((entry): entry is [string, string] => typeof entry[1] === "string")
.map(([key, value]) => `$env:${key}='${escapePowerShellSingleQuoted(value)}'`),
automationCommand
? `& '${escapePowerShellSingleQuoted(automationCommand.executablePath)}' '${escapePowerShellSingleQuoted(automationCommand.scriptPath)}' ${automationCommand.args.map((value) => `'${escapePowerShellSingleQuoted(value)}'`).join(" ")}`
: `& '${escapePowerShellSingleQuoted(paths.nodeExecutable)}' '${escapePowerShellSingleQuoted(runnerScriptPath!)}'`,
"$exitCode = if ($LASTEXITCODE -is [int]) { $LASTEXITCODE } else { 0 }",
"exit $exitCode"
].join("; ");
child = spawn(getWindowsPowerShellPath(), [
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-Command",
wrapperScript
], spawnOptions);
}
const finishWithError = (message: string, errorCategory?: string) => { const finishWithError = (message: string, errorCategory?: string) => {
if (settled) { if (settled) {
......
...@@ -26,6 +26,9 @@ export function isTransientLocalGatewayError(message?: string): boolean { ...@@ -26,6 +26,9 @@ export function isTransientLocalGatewayError(message?: string): boolean {
|| normalized.includes("gateway became ready") || normalized.includes("gateway became ready")
|| normalized.includes("gateway closed during readiness probe") || normalized.includes("gateway closed during readiness probe")
|| normalized.includes("gateway closed before readiness probe completed") || normalized.includes("gateway closed before readiness probe completed")
|| normalized.includes("gateway closed before health probe completed")
|| normalized.includes("timed out while probing reusable gateway")
|| normalized.includes("timed out while waiting for reusable gateway")
|| normalized.includes("bundled runtime exited before gateway became ready"); || normalized.includes("bundled runtime exited before gateway became ready");
} }
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 331 331" role="img" aria-label="千匠问天">
<defs>
<linearGradient id="qjc-ring" x1="46" y1="258" x2="258" y2="35" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#1faeea"/>
<stop offset=".46" stop-color="#2f8ff7"/>
<stop offset="1" stop-color="#6848ff"/>
</linearGradient>
<linearGradient id="qjc-core" x1="113" y1="195" x2="232" y2="87" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#7b26fb"/>
<stop offset=".58" stop-color="#5c55f7"/>
<stop offset="1" stop-color="#5362f8"/>
</linearGradient>
<linearGradient id="qjc-blade" x1="201" y1="72" x2="303" y2="279" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#7150ff"/>
<stop offset=".46" stop-color="#7b42f8"/>
<stop offset="1" stop-color="#8d2df4"/>
</linearGradient>
<linearGradient id="qjc-needle" x1="0" y1="88" x2="137" y2="129" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#22a5f5"/>
<stop offset="1" stop-color="#2e82f3"/>
</linearGradient>
<linearGradient id="qjc-accent" x1="78" y1="72" x2="249" y2="50" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#8e51ff"/>
<stop offset=".52" stop-color="#f0f7ff"/>
<stop offset="1" stop-color="#ffffff"/>
</linearGradient>
</defs>
<path d="M165.5 0C74.1 0 0 74.1 0 165.5c0 49.3 21.6 93.6 55.8 123.9l40.7-56.7a95.5 95.5 0 0 1 69-161.7 95 95 0 0 1 73.3 34.3l56.7-40.8A165 165 0 0 0 165.5 0Z" fill="url(#qjc-ring)"/>
<path d="M165.5 90.9a74.6 74.6 0 1 0 0 149.2 74.6 74.6 0 0 0 0-149.2Zm0 128.7a54.1 54.1 0 1 1 0-108.2 54.1 54.1 0 0 1 0 108.2Z" fill="url(#qjc-core)"/>
<path d="M0 89.4 134.4 116l-2.7 18.7L0 106.8V89.4Z" fill="url(#qjc-needle)"/>
<path d="M79.7 94.9c20.2-30.2 52.5-48.3 89.2-48.3 29.3 0 55.8 11.6 75.1 30.5" fill="none" stroke="url(#qjc-accent)" stroke-width="4.6" stroke-linecap="round"/>
<path d="M88.7 93.2c11.7-14.6 27.7-25.4 46.1-30.6" fill="none" stroke="#8b54ff" stroke-width="4" stroke-linecap="round"/>
<path d="M331 40 179.7 151l21.2 78.7L293.8 323c2.1 2.2 5.7.7 5.7-2.4V230.7L331 194.5V40Z" fill="url(#qjc-blade)"/>
<path d="M331 40 179.7 151l105.7-33.1L331 40Z" fill="#7554ff" opacity=".84"/>
<path d="M331 194.5 270.7 194.8 299.5 230.7 331 194.5Z" fill="#6f3dec" opacity=".7"/>
<path d="M179.7 151 201 229.7l40.7 53.2-18.7-86.8L179.7 151Z" fill="#6a39ed" opacity=".86"/>
<circle cx="165.5" cy="151" r="21.7" fill="#f4f7ff"/>
<path d="M179.7 151 191.1 174.1 165.5 172.7a21.7 21.7 0 0 0 14.2-21.7Z" fill="#e7ecff"/>
</svg>
# Build Notes # Build Notes
- `apps/ui` emits its production bundle into `apps/desktop/dist/renderer` - `apps/ui` emits its production bundle into `apps/desktop/dist/renderer`
- `apps/desktop` packages the final EXE - `apps/desktop` packages the final macOS Apple Silicon DMG on this branch
- `vendor/openclaw-runtime` is reserved for the pinned runtime payload - `vendor/openclaw-runtime` is reserved for the pinned macOS arm64 runtime payload
- macOS formal entry points are `materialize-runtime-payload.mjs`, `pnpm package:mac`, and `pnpm smoke:mac:*`; the PowerShell/NSIS scripts below are legacy Windows validation references and are not the primary packaging path on this branch
- `installer-smoke.ps1` now splits NSIS validation into installer materialization first and installed-app smoke second; it records preflight old-install evidence, classifies installer failures (`empty-exit-zero-files`, `missing-uninstaller-only`, `partial-materialization`, `app-smoke-failure`), retries the empty-exit case once by default, and writes a combined JSON summary instead of only the raw renderer smoke payload; the summary now also records installed runtime payload size/file-count breakdown; `pnpm smoke:installer` - `installer-smoke.ps1` now splits NSIS validation into installer materialization first and installed-app smoke second; it records preflight old-install evidence, classifies installer failures (`empty-exit-zero-files`, `missing-uninstaller-only`, `partial-materialization`, `app-smoke-failure`), retries the empty-exit case once by default, and writes a combined JSON summary instead of only the raw renderer smoke payload; the summary now also records installed runtime payload size/file-count breakdown; `pnpm smoke:installer`
- `electron-smoke.ps1` launches the desktop app directly under Electron with isolated `userData` and `logs` paths, then validates execution-policy smoke output; it now also supports preparing a workspace-entry fixture, preserving `userData`, and remote bundle-specific assertions - `electron-smoke.ps1` launches the desktop app directly under Electron with isolated `userData` and `logs` paths, then validates execution-policy smoke output; it now also supports preparing a workspace-entry fixture, preserving `userData`, and remote bundle-specific assertions
- `materialize-runtime-payload.ps1` generates a local bundled runtime payload under `vendor/openclaw-runtime/` by copying the local `node.exe`, the installed OpenClaw package, a local OpenClaw config snapshot, and a self-contained Python runtime with the locked dependency set installed into it; before the payload is finalized it now strips low-risk non-runtime artifacts such as non-template OpenClaw docs/README files, Python build helpers, `__pycache__`, `.pyc` / `.pyo`, and source maps, while preserving `openclaw/package/docs/reference/templates` required by OpenClaw workspace bootstrap, and writes payload size/file-count telemetry into `runtime-manifest.json`; when the existing payload manifest's `materializationKey` still matches the current inputs, it short-circuits and reuses the payload without rerunning `pip` upgrade or dependency installation - `materialize-runtime-payload.mjs` generates the macOS arm64 bundled runtime payload under `vendor/openclaw-runtime/` by copying `node/bin/node`, the installed OpenClaw package, a local OpenClaw config snapshot, a self-contained `python/bin/python3` runtime, `ffmpeg/bin/ffmpeg`, `ffmpeg/bin/ffprobe`, and Playwright browsers; it writes `platform: "darwin"` / `arch: "arm64"` plus size/file-count telemetry into `runtime-manifest.json`; when the existing payload manifest's `materializationKey` still matches the current inputs, it short-circuits and reuses the payload without rerunning `pip` upgrade or dependency installation
- `mac-runtime-smoke.mjs`, `mac-package-smoke.mjs`, `mac-workspace-entry-smoke.mjs`, and `mac-workspace-service-smoke.mjs` validate the macOS runtime payload, packaged app resources, direct bundled-Python workspace entry, and `ProjectWorkspaceExecutorService` automation path respectively; `pnpm smoke:mac:runtime`, `pnpm smoke:mac:package`, `pnpm smoke:mac:workspace-entry`, `pnpm smoke:mac:workspace-service`
- `materialize-runtime-payload.ps1` is the legacy Windows materializer for the historical NSIS/EXE flow
- `materialize-runtime-cache-smoke.ps1` materializes an isolated runtime directory twice and asserts the first run is a cache miss while the second run is a cache hit that skips `pip` upgrade and locked dependency installation; `pnpm smoke:materialize-cache` - `materialize-runtime-cache-smoke.ps1` materializes an isolated runtime directory twice and asserts the first run is a cache miss while the second run is a cache hit that skips `pip` upgrade and locked dependency installation; `pnpm smoke:materialize-cache`
- `ffmpeg-runtime-smoke.ps1` compiles the targeted `ffmpeg-runtime-smoke.ts` service-level smoke with the local desktop TypeScript toolchain, runs it under Node, and verifies bundled `ffmpeg` / `ffprobe` path resolution plus `FFMPEG_BIN` / `FFPROBE_BIN` injection into workspace automation subprocesses; `pnpm smoke:ffmpeg-runtime` - `ffmpeg-runtime-smoke.ps1` compiles the targeted `ffmpeg-runtime-smoke.ts` service-level smoke with the local desktop TypeScript toolchain, runs it under Node, and verifies bundled `ffmpeg` / `ffprobe` path resolution plus `FFMPEG_BIN` / `FFPROBE_BIN` injection into workspace automation subprocesses; `pnpm smoke:ffmpeg-runtime`
- `bundled-runtime-smoke.ps1` materializes the local runtime payload, forces bundled-runtime mode, and validates that Electron can launch and use the managed runtime end to end - `bundled-runtime-smoke.ps1` materializes the local runtime payload, forces bundled-runtime mode, and validates that Electron can launch and use the managed runtime end to end
...@@ -20,7 +23,7 @@ ...@@ -20,7 +23,7 @@
- `douyin-expert-live-run.ps1` packages `workspace/douyin` into a zip-backed expert bundle, sends an experts-page prompt with a test image attachment, and validates that Douyin writes fresh preview artifacts such as `_latest_workflow_summary.json`, `video_request.json`, `storyboard.json`, and `omnihuman_prompt.txt` inside the synced project; `powershell -ExecutionPolicy Bypass -File build/scripts/douyin-expert-live-run.ps1` - `douyin-expert-live-run.ps1` packages `workspace/douyin` into a zip-backed expert bundle, sends an experts-page prompt with a test image attachment, and validates that Douyin writes fresh preview artifacts such as `_latest_workflow_summary.json`, `video_request.json`, `storyboard.json`, and `omnihuman_prompt.txt` inside the synced project; `powershell -ExecutionPolicy Bypass -File build/scripts/douyin-expert-live-run.ps1`
- Remote project zip delivery and workspace-entry packaging rules are documented in `docs/remote-project-bundle-spec.zh-CN.md` - Remote project zip delivery and workspace-entry packaging rules are documented in `docs/remote-project-bundle-spec.zh-CN.md`
- `default-chat-smoke.ps1` compiles the targeted `default-chat-context-smoke.ts` service-level smoke with the local desktop TypeScript toolchain and verifies `chat-fallback` routing, project context injection into the prepared prompt, post-execution snapshot refresh/rebind, and reuse of the refreshed snapshot on the next request; `pnpm smoke:default-chat` - `default-chat-smoke.ps1` compiles the targeted `default-chat-context-smoke.ts` service-level smoke with the local desktop TypeScript toolchain and verifies `chat-fallback` routing, project context injection into the prepared prompt, post-execution snapshot refresh/rebind, and reuse of the refreshed snapshot on the next request; `pnpm smoke:default-chat`
- `installer-smoke.ps1` validates the packaged Python runtime by importing the preinstalled table/document/web dependencies from `resources/vendor/openclaw-runtime/python/python.exe` - Legacy Windows `installer-smoke.ps1` validates the packaged Python runtime by importing preinstalled table/document/web dependencies from `resources/vendor/openclaw-runtime/python/python.exe`; on this macOS branch use `mac-runtime-smoke.mjs` against `python/bin/python3`
- `installer-smoke.ps1` also validates that the packaged runtime still contains the OpenClaw workspace template fallback file `resources/vendor/openclaw-runtime/openclaw/package/docs/reference/templates/AGENTS.md` before it launches the installed app smoke - `installer-smoke.ps1` also validates that the packaged runtime still contains the OpenClaw workspace template fallback file `resources/vendor/openclaw-runtime/openclaw/package/docs/reference/templates/AGENTS.md` before it launches the installed app smoke
- `installer-path-change-smoke.ps1` installs once to an initial path, reinstalls the same package to a second path, and asserts the relocated run still materializes `Uninstall 千匠问天.exe` while reporting prior-install evidence; `pnpm smoke:installer:path-change` - `installer-path-change-smoke.ps1` installs once to an initial path, reinstalls the same package to a second path, and asserts the relocated run still materializes `Uninstall 千匠问天.exe` while reporting prior-install evidence; `pnpm smoke:installer:path-change`
- `installer-target-residue-smoke.ps1` preseeds the target directory with a stale `Uninstall 千匠问天.exe`, runs the real NSIS installer silently, and verifies the packaged install can overwrite removable residue instead of failing at the uninstaller write step; `pnpm smoke:installer:target-residue` - `installer-target-residue-smoke.ps1` preseeds the target directory with a stale `Uninstall 千匠问天.exe`, runs the real NSIS installer silently, and verifies the packaged install can overwrite removable residue instead of failing at the uninstaller write step; `pnpm smoke:installer:target-residue`
......
#!/usr/bin/env node
import { existsSync, readFileSync, readdirSync } from "node:fs"
import { join, resolve } from "node:path"
const repoRoot = resolve(import.meta.dirname, "..", "..")
const installerDir = join(repoRoot, "dist", "installer")
let failures = 0
function check(label, condition, detail) {
if (condition) {
console.log(` PASS: ${label}`)
} else {
console.error(` FAIL: ${label}${detail ? ` — ${detail}` : ""}`)
failures++
}
}
function findAppDir(dir) {
if (!existsSync(dir)) return null
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.isDirectory() && entry.name.endsWith(".app")) {
return join(dir, entry.name)
}
}
return null
}
console.log("mac-package-smoke: validating electron-builder output")
// 1. Check for .app directory
const macArm64Dir = join(installerDir, "mac-arm64")
const appDir = findAppDir(macArm64Dir) || findAppDir(installerDir)
check(".app directory exists", Boolean(appDir), `searched in: ${macArm64Dir} and ${installerDir}`)
if (appDir) {
// 2. Check for vendor/openclaw-runtime in Resources
const resourcesDir = join(appDir, "Contents", "Resources")
const runtimeDir = join(resourcesDir, "vendor", "openclaw-runtime")
check("vendor/openclaw-runtime in app Resources", existsSync(runtimeDir), `not found: ${runtimeDir}`)
if (existsSync(runtimeDir)) {
const manifestPath = join(runtimeDir, "runtime-manifest.json")
check("runtime-manifest.json in Resources", existsSync(manifestPath))
check("node/bin/node in Resources", existsSync(join(runtimeDir, "node", "bin", "node")))
check("python/bin/python3 in Resources", existsSync(join(runtimeDir, "python", "bin", "python3")))
if (existsSync(manifestPath)) {
try {
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"))
check("packaged manifest platform is darwin", manifest.platform === "darwin", `got: ${manifest.platform}`)
check("packaged manifest arch is arm64", manifest.arch === "arm64", `got: ${manifest.arch}`)
} catch (e) {
check("packaged runtime-manifest.json is valid JSON", false, e.message)
}
}
}
// 3. Check for app.asar
const asarPath = join(resourcesDir, "app.asar")
check("app.asar exists", existsSync(asarPath), `not found: ${asarPath}`)
// 4. Check Info.plist
const plistPath = join(appDir, "Contents", "Info.plist")
check("Info.plist exists", existsSync(plistPath))
}
// 5. Check for DMG file (optional, --dir mode doesn't produce one)
const dmgFiles = existsSync(installerDir)
? readdirSync(installerDir, { withFileTypes: true })
.filter(e => e.isFile() && e.name.endsWith(".dmg"))
.map(e => e.name)
: []
if (dmgFiles.length > 0) {
console.log(` INFO: Found DMG file(s): ${dmgFiles.join(", ")}`)
}
// Summary
console.log("")
if (failures === 0) {
console.log("mac-package-smoke: ALL PASSED")
} else {
console.error(`mac-package-smoke: ${failures} FAILURE(S)`)
process.exit(1)
}
#!/usr/bin/env node
import { existsSync, readFileSync } from "node:fs"
import { execFileSync } from "node:child_process"
import { join, resolve } from "node:path"
const repoRoot = resolve(import.meta.dirname, "..", "..")
const runtimeDir = process.env.QJCLAW_SMOKE_RUNTIME_DIR || join(repoRoot, "vendor", "openclaw-runtime")
let failures = 0
function check(label, condition, detail) {
if (condition) {
console.log(` PASS: ${label}`)
} else {
console.error(` FAIL: ${label}${detail ? ` — ${detail}` : ""}`)
failures++
}
}
function checkFile(label, filePath) {
check(label, existsSync(filePath), `not found: ${filePath}`)
}
function checkExec(label, cmd, args) {
try {
const stdout = execFileSync(cmd, args, { encoding: "utf8" }).trim()
check(label, true, stdout)
} catch (e) {
check(label, false, e.message)
}
}
console.log(`mac-runtime-smoke: validating ${runtimeDir}`)
// 1. Manifest
const manifestPath = join(runtimeDir, "runtime-manifest.json")
checkFile("runtime-manifest.json exists", manifestPath)
let manifest = null
if (existsSync(manifestPath)) {
try {
manifest = JSON.parse(readFileSync(manifestPath, "utf8"))
check("manifest platform is darwin", manifest.platform === "darwin", `got: ${manifest.platform}`)
check("manifest arch is arm64", manifest.arch === "arm64", `got: ${manifest.arch}`)
check("manifest bundledNodeExecutable is node/bin/node", manifest.bundledNodeExecutable === "node/bin/node", `got: ${manifest.bundledNodeExecutable}`)
check("manifest pythonExecutable is python/bin/python3", manifest.pythonExecutable === "python/bin/python3", `got: ${manifest.pythonExecutable}`)
check("manifest ffmpegExecutable is ffmpeg/bin/ffmpeg", manifest.ffmpegExecutable === "ffmpeg/bin/ffmpeg", `got: ${manifest.ffmpegExecutable}`)
check("manifest ffprobeExecutable is ffmpeg/bin/ffprobe", manifest.ffprobeExecutable === "ffmpeg/bin/ffprobe", `got: ${manifest.ffprobeExecutable}`)
} catch (e) {
check("manifest is valid JSON", false, e.message)
}
}
// 2. Required files
checkFile("node/bin/node", join(runtimeDir, "node", "bin", "node"))
checkFile("openclaw/index.js", join(runtimeDir, "openclaw", "index.js"))
checkFile("openclaw/package/openclaw.mjs", join(runtimeDir, "openclaw", "package", "openclaw.mjs"))
checkFile("openclaw/package/package.json", join(runtimeDir, "openclaw", "package", "package.json"))
checkFile("config/openclaw.json", join(runtimeDir, "config", "openclaw.json"))
checkFile("python/bin/python3", join(runtimeDir, "python", "bin", "python3"))
checkFile("python/python-manifest.json", join(runtimeDir, "python", "python-manifest.json"))
checkFile("python/runtime-requirements.lock.txt", join(runtimeDir, "python", "runtime-requirements.lock.txt"))
checkFile("ffmpeg/bin/ffmpeg", join(runtimeDir, "ffmpeg", "bin", "ffmpeg"))
checkFile("ffmpeg/bin/ffprobe", join(runtimeDir, "ffmpeg", "bin", "ffprobe"))
checkFile("playwright-browsers", join(runtimeDir, "playwright-browsers"))
checkFile("README.md", join(runtimeDir, "README.md"))
// 3. Executable probes
const nodeExe = join(runtimeDir, "node", "bin", "node")
const pythonExe = join(runtimeDir, "python", "bin", "python3")
if (existsSync(nodeExe)) {
checkExec("node --version", nodeExe, ["--version"])
}
if (existsSync(pythonExe)) {
checkExec("python3 --version", pythonExe, ["--version"])
try {
const stdout = execFileSync(pythonExe, ["-c", "import openpyxl, pandas, requests, fastapi, imageio_ffmpeg, qiniu; print('ok')"], {
encoding: "utf8",
timeout: 15000,
}).trim()
check("python key imports", stdout === "ok", `got: ${stdout}`)
} catch (e) {
check("python key imports", false, e.message?.split("\n")[0])
}
}
// 4. Config probe
const configPath = join(runtimeDir, "config", "openclaw.json")
if (existsSync(configPath)) {
try {
const config = JSON.parse(readFileSync(configPath, "utf8"))
check("config gateway.mode is local", config.gateway?.mode === "local", `got: ${config.gateway?.mode}`)
check("config gateway.bind is loopback", config.gateway?.bind === "loopback", `got: ${config.gateway?.bind}`)
} catch (e) {
check("config is valid JSON", false, e.message)
}
}
// Summary
console.log("")
if (failures === 0) {
console.log("mac-runtime-smoke: ALL PASSED")
} else {
console.error(`mac-runtime-smoke: ${failures} FAILURE(S)`)
process.exit(1)
}
#!/usr/bin/env node
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs"
import { execFileSync } from "node:child_process"
import { join, resolve } from "node:path"
const repoRoot = resolve(import.meta.dirname, "..", "..")
const runtimeDir = process.env.QJCLAW_SMOKE_RUNTIME_DIR || join(repoRoot, "vendor", "openclaw-runtime")
let failures = 0
function check(label, condition, detail) {
if (condition) {
console.log(` PASS: ${label}`)
} else {
console.error(` FAIL: ${label}${detail ? ` — ${detail}` : ""}`)
failures++
}
}
console.log("mac-workspace-entry-smoke: validating workspace automation via bundled Python")
const pythonExe = join(runtimeDir, "python", "bin", "python3")
const nodeExe = join(runtimeDir, "node", "bin", "node")
if (!existsSync(pythonExe)) {
console.error(` FATAL: python3 not found at ${pythonExe}`)
process.exit(1)
}
// 1. Create a minimal test project
const tmpDir = join(repoRoot, ".workspace-smoke-tmp")
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true })
mkdirSync(tmpDir, { recursive: true })
// Write project.json with Python automation
const projectJson = {
workspaceAutomation: {
runtime: "python",
script: "run.py",
args: []
}
}
writeFileSync(join(tmpDir, "project.json"), JSON.stringify(projectJson, null, 2), "utf8")
// Write a minimal run.py that prints a known marker
const runPy = `
import sys
import json
print("QJC_WORKSPACE_EVENT\\t" + json.dumps({"type": "started", "runId": "smoke-test"}))
print("QJC_WORKSPACE_EVENT\\t" + json.dumps({"type": "completed", "content": "workspace smoke ok"}))
sys.exit(0)
`
writeFileSync(join(tmpDir, "run.py"), runPy, "utf8")
// 2. Execute Python automation script
try {
const stdout = execFileSync(pythonExe, [join(tmpDir, "run.py")], {
encoding: "utf8",
timeout: 15000,
cwd: tmpDir,
env: {
...process.env,
PYTHONUTF8: "1",
PYTHONIOENCODING: "utf-8",
}
})
const lines = stdout.trim().split("\n")
const startedLine = lines.find(l => l.includes('"type": "started"'))
const completedLine = lines.find(l => l.includes('"type": "completed"'))
check("Python automation produced started event", Boolean(startedLine))
check("Python automation produced completed event", Boolean(completedLine))
if (completedLine) {
const eventData = JSON.parse(completedLine.split("\t")[1])
check("completed event content matches", eventData.content === "workspace smoke ok", `got: ${eventData.content}`)
}
} catch (e) {
check("Python automation execution", false, e.message?.split("\n")[0])
}
// 3. Test Node executable can run
if (existsSync(nodeExe)) {
try {
const stdout = execFileSync(nodeExe, ["-e", "console.log('node-ok')"], {
encoding: "utf8",
timeout: 5000,
}).trim()
check("Node bundled executable works", stdout === "node-ok", `got: ${stdout}`)
} catch (e) {
check("Node bundled executable works", false, e.message?.split("\n")[0])
}
}
// Cleanup
rmSync(tmpDir, { recursive: true, force: true })
// Summary
console.log("")
if (failures === 0) {
console.log("mac-workspace-entry-smoke: ALL PASSED")
} else {
console.error(`mac-workspace-entry-smoke: ${failures} FAILURE(S)`)
process.exit(1)
}
#!/usr/bin/env node
import { execFileSync } from "node:child_process"
import { createRequire } from "node:module"
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
import { join, resolve } from "node:path"
const repoRoot = resolve(import.meta.dirname, "..", "..")
const runtimeDir = process.env.QJCLAW_SMOKE_RUNTIME_DIR || join(repoRoot, "vendor", "openclaw-runtime")
const tmpRoot = join(repoRoot, ".tmp", "mac-workspace-service-smoke")
const compileDir = join(tmpRoot, "compiled")
const projectRoot = join(tmpRoot, "project")
const runtimeDataDir = join(tmpRoot, "runtime-data")
let failures = 0
function check(label, condition, detail) {
if (condition) {
console.log(` PASS: ${label}`)
} else {
console.error(` FAIL: ${label}${detail ? ` — ${detail}` : ""}`)
failures++
}
}
function checkFile(label, filePath) {
check(label, existsSync(filePath), `not found: ${filePath}`)
}
function emitSummary() {
console.log("")
if (failures === 0) {
console.log("mac-workspace-service-smoke: ALL PASSED")
} else {
console.error(`mac-workspace-service-smoke: ${failures} FAILURE(S)`)
process.exit(1)
}
}
async function main() {
console.log("mac-workspace-service-smoke: validating ProjectWorkspaceExecutorService with bundled Python")
const pythonExe = join(runtimeDir, "python", "bin", "python3")
const nodeExe = join(runtimeDir, "node", "bin", "node")
const ffmpegExe = join(runtimeDir, "ffmpeg", "bin", "ffmpeg")
const ffprobeExe = join(runtimeDir, "ffmpeg", "bin", "ffprobe")
const playwrightBrowsersPath = join(runtimeDir, "playwright-browsers")
checkFile("runtime-manifest.json", join(runtimeDir, "runtime-manifest.json"))
checkFile("bundled python3", pythonExe)
checkFile("bundled node", nodeExe)
if (failures > 0) {
emitSummary()
return
}
rmSync(tmpRoot, { recursive: true, force: true })
mkdirSync(compileDir, { recursive: true })
mkdirSync(projectRoot, { recursive: true })
mkdirSync(join(runtimeDataDir, "state"), { recursive: true })
mkdirSync(join(runtimeDataDir, "logs"), { recursive: true })
execFileSync("corepack", [
"pnpm",
"--dir",
join(repoRoot, "apps", "desktop"),
"exec",
"tsup",
"src/main/services/project-workspace-executor.ts",
"--no-config",
"--format",
"cjs",
"--platform",
"node",
"--target",
"node20",
"--out-dir",
compileDir,
"--external",
"electron",
], {
cwd: repoRoot,
stdio: "inherit",
})
const compiledService = join(compileDir, "project-workspace-executor.js")
checkFile("compiled ProjectWorkspaceExecutorService", compiledService)
if (failures > 0) {
emitSummary()
return
}
const projectJson = {
workspaceAutomation: {
runtime: "python",
script: "run.py",
args: ["--prompt", "{prompt}", "--session", "{sessionId}"],
env: {
QJC_SMOKE_PROJECT_ROOT: "{projectRoot}",
QJC_SMOKE_ATTACHMENT_PATHS: "{attachmentPathsJson}",
},
},
}
writeFileSync(join(projectRoot, "project.json"), JSON.stringify(projectJson, null, 2), "utf8")
const runPy = `
import argparse
import json
import os
import sys
parser = argparse.ArgumentParser()
parser.add_argument("--prompt", required=True)
parser.add_argument("--session", required=True)
args = parser.parse_args()
checks = {
"prompt": args.prompt,
"session": args.session,
"project_root": os.environ.get("QJC_SMOKE_PROJECT_ROOT", ""),
"runtime_dir": os.environ.get("QJCLAW_BUNDLED_RUNTIME_DIR", ""),
"playwright": os.environ.get("PLAYWRIGHT_BROWSERS_PATH", ""),
"ffmpeg": os.environ.get("FFMPEG_BIN", ""),
"ffprobe": os.environ.get("FFPROBE_BIN", ""),
"python": sys.executable,
}
missing = [
name for name in ["project_root", "runtime_dir", "playwright", "python"]
if not checks[name]
]
if missing:
print("QJC_WORKSPACE_EVENT\\t" + json.dumps({
"type": "error",
"message": "missing env: " + ",".join(missing),
}))
sys.exit(0)
print("QJC_WORKSPACE_EVENT\\t" + json.dumps({
"type": "started",
"runId": "mac-workspace-service-smoke-run",
}))
print("QJC_WORKSPACE_EVENT\\t" + json.dumps({
"type": "status",
"stage": "python-automation",
"label": "Bundled Python automation started",
}))
print("QJC_WORKSPACE_EVENT\\t" + json.dumps({
"type": "completed",
"content": "service smoke ok: " + json.dumps(checks, sort_keys=True),
}))
`.trimStart()
writeFileSync(join(projectRoot, "run.py"), runPy, "utf8")
const require = createRequire(import.meta.url)
const { ProjectWorkspaceExecutorService } = require(compiledService)
const statuses = []
const started = []
let syncedAction = null
const runtimeManager = {
status: async () => ({ payloadState: "ready" }),
syncManagedConfig: async (action) => {
syncedAction = action
},
resolveBundledPaths: () => ({
runtimeDir,
nodeExecutable: nodeExe,
openClawEntry: join(runtimeDir, "openclaw", "index.js"),
packagedOpenClawEntry: join(runtimeDir, "openclaw", "package", "openclaw.mjs"),
runtimeManifestPath: join(runtimeDir, "runtime-manifest.json"),
defaultConfigPath: join(runtimeDir, "config", "openclaw.json"),
pythonExecutable: pythonExe,
pythonManifestPath: join(runtimeDir, "python", "python-manifest.json"),
ffmpegExecutable: existsSync(ffmpegExe) ? ffmpegExe : undefined,
ffprobeExecutable: existsSync(ffprobeExe) ? ffprobeExe : undefined,
playwrightBrowsersPath,
managedConfigPath: join(runtimeDataDir, "state", "openclaw.runtime.json"),
readmePath: join(runtimeDir, "README.md"),
runtimeDataDir,
runtimeStateDir: join(runtimeDataDir, "state"),
runtimeLogsDir: join(runtimeDataDir, "logs"),
logFilePath: join(runtimeDataDir, "logs", "runtime.log"),
}),
}
try {
const service = new ProjectWorkspaceExecutorService(runtimeManager)
const result = await service.execute({
sessionId: "mac-workspace-service-smoke-session",
projectRoot,
prompt: "prepared smoke prompt",
userPrompt: "human smoke prompt",
runId: "outer-smoke-run",
attachments: [],
}, {
onStarted: (runId) => started.push(runId),
onStatus: (stage, label) => statuses.push({ stage, label }),
})
check("syncManagedConfig called with sync", syncedAction === "sync", `got: ${syncedAction}`)
check("service observed started event", started.includes("mac-workspace-service-smoke-run"), `got: ${started.join(", ")}`)
check("service observed status event", statuses.some((entry) => entry.stage === "python-automation"), JSON.stringify(statuses))
check("service returned assistant reply", Boolean(result.reply), JSON.stringify(result))
check("service reply content matches", result.reply?.content?.includes("service smoke ok"), result.reply?.content)
check("service used bundled python", result.reply?.content?.includes(pythonExe), result.reply?.content)
check("service injected runtime dir", result.reply?.content?.includes(runtimeDir), result.reply?.content)
} catch (error) {
check("ProjectWorkspaceExecutorService execution", false, error instanceof Error ? error.message : String(error))
} finally {
rmSync(tmpRoot, { recursive: true, force: true })
}
emitSummary()
}
main().catch((error) => {
console.error(error instanceof Error ? error.stack || error.message : String(error))
process.exit(1)
})
This diff is collapsed.
...@@ -51,8 +51,45 @@ function createConfig(overrides: Partial<AppConfig> = {}): AppConfig { ...@@ -51,8 +51,45 @@ function createConfig(overrides: Partial<AppConfig> = {}): AppConfig {
copywriting: { copywriting: {
baseUrl: "", baseUrl: "",
apiKeyConfigured: false apiKeyConfigured: false
},
digitalHuman: {
volcRegion: "cn-north-1",
volcService: "cv",
volcHost: "visual.volcengineapi.com",
volcScheme: "https",
ttsVoice: "zh-CN-YunxiNeural",
qiniuBucket: "alketas",
qiniuDomain: "http://tcwwu6wg4.hd-bkt.clouddn.com",
qiniuKeyPrefix: "omnihuman",
volcAccessKeyConfigured: false,
volcSecretKeyConfigured: false,
qiniuAccessKeyConfigured: false,
qiniuSecretKeyConfigured: false
}
},
douyinRuntimeConfig: {
videoAnalyzer: {
baseUrl: "",
apiKeyConfigured: false,
modelId: ""
},
replicationBrief: {
baseUrl: "",
apiKeyConfigured: false,
modelId: ""
},
vectcut: {
baseUrl: "",
fileBaseUrl: "",
apiKeyConfigured: false
} }
}, },
xhsFeishuConfig: {
appIdConfigured: false,
appSecretConfigured: false,
appTokenConfigured: false,
tableIdConfigured: false
},
...overrides ...overrides
}; };
} }
...@@ -137,6 +174,9 @@ async function main(): Promise<void> { ...@@ -137,6 +174,9 @@ async function main(): Promise<void> {
"Gateway closed during connect (1006).", "Gateway closed during connect (1006).",
"Gateway closed during connect (1005).", "Gateway closed during connect (1005).",
"Gateway closed during connect (1000).", "Gateway closed during connect (1000).",
"Gateway closed before health probe completed (1000).",
"Timed out while probing reusable Gateway status and health at ws://127.0.0.1:18889.",
"Timed out while waiting for reusable Gateway at ws://127.0.0.1:18889.",
"connect ECONNREFUSED 127.0.0.1:18889" "connect ECONNREFUSED 127.0.0.1:18889"
]; ];
for (const message of transientCodes) { for (const message of transientCodes) {
......
This diff is collapsed.
...@@ -8,11 +8,17 @@ ...@@ -8,11 +8,17 @@
"clean": "corepack pnpm -r run clean", "clean": "corepack pnpm -r run clean",
"dev": "corepack pnpm --parallel --filter @qjclaw/ui --filter @qjclaw/desktop dev", "dev": "corepack pnpm --parallel --filter @qjclaw/ui --filter @qjclaw/desktop dev",
"lint": "corepack pnpm -r run lint", "lint": "corepack pnpm -r run lint",
"package": "corepack pnpm run materialize:runtime && corepack pnpm build && corepack pnpm --filter @qjclaw/desktop exec electron-builder --config electron-builder.yml", "package": "corepack pnpm run package:mac",
"package:mac": "corepack pnpm run materialize:runtime:mac && corepack pnpm build && corepack pnpm --filter @qjclaw/desktop exec electron-builder --config electron-builder.yml --mac dmg --arm64",
"typecheck": "corepack pnpm -r run typecheck", "typecheck": "corepack pnpm -r run typecheck",
"smoke:installer": "powershell -ExecutionPolicy Bypass -File build/scripts/installer-smoke.ps1", "smoke:installer": "powershell -ExecutionPolicy Bypass -File build/scripts/installer-smoke.ps1",
"smoke:execution-policy": "powershell -ExecutionPolicy Bypass -File build/scripts/electron-smoke.ps1", "smoke:execution-policy": "powershell -ExecutionPolicy Bypass -File build/scripts/electron-smoke.ps1",
"materialize:runtime": "powershell -ExecutionPolicy Bypass -File build/scripts/materialize-runtime-payload.ps1", "materialize:runtime": "powershell -ExecutionPolicy Bypass -File build/scripts/materialize-runtime-payload.ps1",
"materialize:runtime:mac": "node build/scripts/materialize-runtime-payload.mjs",
"smoke:mac:runtime": "node build/scripts/mac-runtime-smoke.mjs",
"smoke:mac:package": "node build/scripts/mac-package-smoke.mjs",
"smoke:mac:workspace-entry": "node build/scripts/mac-workspace-entry-smoke.mjs",
"smoke:mac:workspace-service": "node build/scripts/mac-workspace-service-smoke.mjs",
"smoke:bundled-runtime": "powershell -ExecutionPolicy Bypass -File build/scripts/bundled-runtime-smoke.ps1", "smoke:bundled-runtime": "powershell -ExecutionPolicy Bypass -File build/scripts/bundled-runtime-smoke.ps1",
"smoke:workspace-entry": "powershell -ExecutionPolicy Bypass -File build/scripts/workspace-entry-smoke.ps1", "smoke:workspace-entry": "powershell -ExecutionPolicy Bypass -File build/scripts/workspace-entry-smoke.ps1",
"smoke:cloud-bundle": "powershell -ExecutionPolicy Bypass -File build/scripts/cloud-bundle-smoke.ps1", "smoke:cloud-bundle": "powershell -ExecutionPolicy Bypass -File build/scripts/cloud-bundle-smoke.ps1",
...@@ -54,5 +60,3 @@ ...@@ -54,5 +60,3 @@
] ]
} }
} }
This diff is collapsed.
...@@ -2,15 +2,15 @@ ...@@ -2,15 +2,15 @@
Immutable packaged payload under `vendor/openclaw-runtime/` includes: Immutable packaged payload under `vendor/openclaw-runtime/` includes:
- `node/node.exe` - `node/bin/node`
- `openclaw/index.js` - `openclaw/index.js`
- `openclaw/package/openclaw.mjs` - `openclaw/package/openclaw.mjs`
- `config/openclaw.json` - `config/openclaw.json`
- `python/python.exe` - `python/bin/python3`
- `python/python-manifest.json` - `python/python-manifest.json`
- `python/runtime-requirements.lock.txt` - `python/runtime-requirements.lock.txt`
- `ffmpeg/bin/ffmpeg.exe` - `ffmpeg/bin/ffmpeg`
- `ffmpeg/bin/ffprobe.exe` - `ffmpeg/bin/ffprobe`
- `playwright-browsers/` - `playwright-browsers/`
Mutable runtime data lives outside the installer payload and should be created under Electron `userData/runtime/`. Mutable runtime data lives outside the installer payload and should be created under Electron `userData/runtime/`.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment