chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:12:33 +08:00
commit aacb60a4af
3387 changed files with 981307 additions and 0 deletions
+317
View File
@@ -0,0 +1,317 @@
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawnSync } from 'node:child_process'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '..', '..')
const runtimeGatewayDir = join(packageRoot, 'runtime', 'gateway')
const pyinstallerWorkDir = join(packageRoot, '.pyinstaller')
const entryPath = join(scriptDir, 'gateway-entry.py')
const controlUiDistDir = join(repoRoot, 'src', 'opensquilla', 'gateway', 'static', 'dist')
const routerBundleDir = join(repoRoot, 'src', 'opensquilla', 'squilla_router', 'models', 'v4.2_phase3_inference')
const addDataSeparator = process.platform === 'win32' ? ';' : ':'
const gitLfsPointerHeader = 'version https://git-lfs.github.com/spec/v1'
function findFilesByName(root, fileName) {
const matches = []
function walk(dir) {
let entries
try {
entries = readdirSync(dir, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
const path = join(dir, entry.name)
if (entry.isDirectory()) {
walk(path)
} else if (entry.isFile() && entry.name === fileName) {
matches.push(path)
}
}
}
walk(root)
return matches
}
function findMacLibomp() {
const candidates = []
candidates.push(...findFilesByName(runtimeGatewayDir, 'libomp.dylib'))
const brew = spawnSync('brew', ['--prefix', 'libomp'], {
encoding: 'utf8',
windowsHide: true,
})
if (brew.status === 0) {
const prefix = brew.stdout.trim()
if (prefix) candidates.push(join(prefix, 'lib', 'libomp.dylib'))
}
candidates.push(
'/opt/homebrew/opt/libomp/lib/libomp.dylib',
'/usr/local/opt/libomp/lib/libomp.dylib',
'/opt/local/lib/libomp/libomp.dylib',
)
return candidates.find((candidate) => existsSync(candidate))
}
function signMacBinary(path) {
if (process.platform !== 'darwin') return
const result = spawnSync('codesign', ['--force', '--sign', '-', path], {
encoding: 'utf8',
windowsHide: true,
})
if (result.error) throw result.error
if (result.status !== 0) {
throw new Error(`codesign failed for ${path} with exit ${result.status}:\n${result.stderr || result.stdout}`)
}
}
function pythonPackageFile(packageName, relativePath) {
const code = [
'import importlib.util',
'import pathlib',
'import sys',
`spec = importlib.util.find_spec(${JSON.stringify(packageName)})`,
'if spec is None or spec.origin is None:',
' sys.exit(1)',
`path = pathlib.Path(spec.origin).parent / ${JSON.stringify(relativePath)}`,
'if not path.exists():',
' sys.exit(2)',
'print(path)',
].join('\n')
const result = spawnSync(
'uv',
['run', '--extra', 'recommended', 'python', '-c', code],
{
cwd: repoRoot,
env: {
...process.env,
PYTHONUNBUFFERED: '1',
PYTHONUTF8: '1',
PYTHONIOENCODING: 'utf-8:replace',
},
encoding: 'utf8',
windowsHide: true,
},
)
if (result.error) throw result.error
if (result.status !== 0) {
throw new Error(
`Could not locate ${packageName}/${relativePath} for the desktop gateway bundle.\n${result.stderr || result.stdout}`,
)
}
const path = result.stdout.trim().split(/\r?\n/).filter(Boolean).at(-1)
if (!path || !existsSync(path)) {
throw new Error(`Resolved ${packageName}/${relativePath} to an invalid path: ${path || '<empty>'}`)
}
return path
}
function addBinaryArg(sourcePath, destinationDir) {
return ['--add-binary', `${sourcePath}${addDataSeparator}${destinationDir}`]
}
function platformLightgbmLibraryPath() {
if (process.platform === 'win32') return join('bin', 'lib_lightgbm.dll')
if (process.platform === 'darwin') return join('lib', 'lib_lightgbm.dylib')
return join('lib', 'lib_lightgbm.so')
}
function platformLightgbmBundleDir() {
return process.platform === 'win32' ? 'lightgbm/bin' : 'lightgbm/lib'
}
function readFileHead(path, bytes = 96) {
return readFileSync(path).subarray(0, bytes).toString('utf8')
}
function assertRouterAssetsReady() {
const manifestPath = join(routerBundleDir, 'artifact_manifest.json')
if (!existsSync(manifestPath)) {
throw new Error(`Router artifact manifest not found at ${manifestPath}.`)
}
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
const problems = []
for (const file of manifest.files || []) {
const relPath = String(file.path || '')
if (!relPath) continue
const path = join(routerBundleDir, relPath)
if (!existsSync(path)) {
problems.push(`${relPath}: missing`)
continue
}
const actualSize = statSync(path).size
if (typeof file.size_bytes === 'number' && actualSize !== file.size_bytes) {
problems.push(`${relPath}: size ${actualSize} != manifest ${file.size_bytes}`)
continue
}
if (readFileHead(path).startsWith(gitLfsPointerHeader)) {
problems.push(`${relPath}: Git LFS pointer file, not the real router artifact`)
}
}
if (problems.length > 0) {
throw new Error(
[
'Router V4 Phase 3 assets are incomplete; refusing to build a desktop gateway that degrades routing.',
'Run `git lfs pull --include="src/opensquilla/squilla_router/models/v4.2_phase3_inference/**"` and rebuild.',
...problems.map((problem) => `- ${problem}`),
].join('\n'),
)
}
}
function patchMacLightgbmRuntime() {
if (process.platform !== 'darwin') return
const lightgbmLibs = findFilesByName(runtimeGatewayDir, 'lib_lightgbm.dylib')
if (lightgbmLibs.length === 0) {
throw new Error('LightGBM was requested for the desktop gateway, but lib_lightgbm.dylib was not bundled.')
}
const libomp = findMacLibomp()
if (!libomp) {
throw new Error(
'macOS LightGBM runtime requires libomp.dylib. Ensure scikit-learn is bundled, or install libomp on the build host, for example `brew install libomp`, then rebuild the desktop gateway.',
)
}
for (const lightgbmLib of lightgbmLibs) {
const bundledLibomp = join(dirname(lightgbmLib), 'libomp.dylib')
copyFileSync(libomp, bundledLibomp)
signMacBinary(bundledLibomp)
const result = spawnSync(
'install_name_tool',
['-change', '@rpath/libomp.dylib', '@loader_path/libomp.dylib', lightgbmLib],
{ encoding: 'utf8', windowsHide: true },
)
if (result.error) throw result.error
if (result.status !== 0) {
throw new Error(
`install_name_tool failed for ${lightgbmLib} with exit ${result.status}:\n${result.stderr || result.stdout}`,
)
}
signMacBinary(lightgbmLib)
}
}
if (!existsSync(join(controlUiDistDir, 'index.html'))) {
throw new Error(`Built Control UI not found at ${controlUiDistDir}. Run npm run build:web before npm run build:gateway.`)
}
assertRouterAssetsReady()
rmSync(runtimeGatewayDir, { recursive: true, force: true })
mkdirSync(runtimeGatewayDir, { recursive: true })
mkdirSync(pyinstallerWorkDir, { recursive: true })
const lightgbmBinaryArgs = addBinaryArg(
pythonPackageFile('lightgbm', platformLightgbmLibraryPath()),
platformLightgbmBundleDir(),
)
const macOpenMpBinaryArgs = process.platform === 'darwin'
? addBinaryArg(pythonPackageFile('sklearn', join('.dylibs', 'libomp.dylib')), '.')
: []
const args = [
'run',
'--extra',
'recommended',
'--extra',
'mcp',
'--extra',
'msg',
'--extra',
'matrix',
'--extra',
'document-extras',
'--with',
'pyinstaller',
'pyinstaller',
'--noconfirm',
'--clean',
'--onedir',
'--name',
'opensquilla-gateway',
'--distpath',
runtimeGatewayDir,
'--workpath',
pyinstallerWorkDir,
'--specpath',
pyinstallerWorkDir,
'--collect-all',
'opensquilla',
'--collect-all',
'sqlite_vec',
'--collect-binaries',
'sklearn',
'--copy-metadata',
'opensquilla',
'--copy-metadata',
'scikit-learn',
'--copy-metadata',
'lightgbm',
'--copy-metadata',
'yoyo-migrations',
'--hidden-import',
'joblib',
'--hidden-import',
'sklearn',
'--hidden-import',
'sklearn.feature_extraction.text',
'--hidden-import',
'sklearn.decomposition._truncated_svd',
'--hidden-import',
'sklearn.decomposition._pca',
'--hidden-import',
'sklearn.preprocessing._data',
'--hidden-import',
'lightgbm',
'--hidden-import',
'tokenizers',
'--hidden-import',
'tiktoken',
'--hidden-import',
'onnxruntime',
'--hidden-import',
'mcp',
'--hidden-import',
'yoyo.backends.core.sqlite3',
'--add-data',
`${join(repoRoot, 'migrations')}${addDataSeparator}opensquilla/_migrations`,
'--add-data',
`${controlUiDistDir}${addDataSeparator}opensquilla/gateway/static/dist`,
...lightgbmBinaryArgs,
...macOpenMpBinaryArgs,
entryPath,
]
const result = spawnSync('uv', args, {
cwd: repoRoot,
env: {
...process.env,
PYTHONUNBUFFERED: '1',
PYTHONUTF8: '1',
PYTHONIOENCODING: 'utf-8:replace',
},
stdio: 'inherit',
})
if (result.error) {
throw result.error
}
if (result.status !== 0) {
process.exit(result.status ?? 1)
}
patchMacLightgbmRuntime()
@@ -0,0 +1,4 @@
from opensquilla.cli.main import app
if __name__ == "__main__":
app()
+391
View File
@@ -0,0 +1,391 @@
import { spawn } from 'node:child_process'
import { createServer } from 'node:net'
import { mkdtemp, mkdir, readdir, rm, writeFile } from 'node:fs/promises'
import { statSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import process from 'node:process'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '..', '..')
const desktopOutputDir = join(repoRoot, 'dist', 'desktop-electron')
const sourceRuntimeGatewayDir = join(packageRoot, 'runtime', 'gateway')
const binaryName = process.platform === 'win32' ? 'opensquilla-gateway.exe' : 'opensquilla-gateway'
const deadlineMs = Number.parseInt(process.env.OPENSQUILLA_GATEWAY_SMOKE_TIMEOUT_MS || '90000', 10)
const pollIntervalMs = 250
const killGraceMs = 3_000
const maxTailLines = 80
function appendTail(tail, chunk) {
const lines = chunk
.toString()
.split(/\r?\n/)
.filter((line) => line.length > 0)
if (lines.length === 0) return tail
return [...tail, ...lines].slice(-maxTailLines)
}
function formatTail(stdoutTail, stderrTail) {
const parts = []
if (stdoutTail.length > 0) parts.push(`stdout tail:\n${stdoutTail.join('\n')}`)
if (stderrTail.length > 0) parts.push(`stderr tail:\n${stderrTail.join('\n')}`)
return parts.length > 0 ? `\n\n${parts.join('\n\n')}` : ''
}
function pathIsFile(path) {
try {
return statSync(path).isFile()
} catch {
return false
}
}
function pathIsDirectory(path) {
try {
return statSync(path).isDirectory()
} catch {
return false
}
}
function gatewayBinaryCandidates(runtimeGatewayDir) {
return [join(runtimeGatewayDir, 'opensquilla-gateway', binaryName), join(runtimeGatewayDir, binaryName)]
}
function findGatewayBinary(runtimeGatewayDir) {
return gatewayBinaryCandidates(runtimeGatewayDir).find(pathIsFile)
}
async function findGeneratedBundleRuntimes(root) {
const runtimes = []
const seenResourcesDirs = new Set()
if (!pathIsDirectory(root)) return runtimes
function addRuntime(label, resourcesDir, platform) {
if (platform !== process.platform || seenResourcesDirs.has(resourcesDir)) return
seenResourcesDirs.add(resourcesDir)
runtimes.push({
label,
runtimeGatewayDir: join(resourcesDir, 'runtime', 'gateway'),
})
}
async function walk(dir, depth) {
if (depth > 5) return
const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
for (const entry of entries) {
if (!entry.isDirectory()) continue
const path = join(dir, entry.name)
if (entry.name.endsWith('.app')) {
addRuntime(`generated app bundle ${path}`, join(path, 'Contents', 'Resources'), 'darwin')
} else if (entry.name === 'win-unpacked' || entry.name === 'linux-unpacked') {
addRuntime(`generated bundle ${path}`, join(path, 'resources'), entry.name === 'win-unpacked' ? 'win32' : 'linux')
} else {
await walk(path, depth + 1)
}
}
}
await walk(root, 0)
return runtimes.sort((left, right) => left.runtimeGatewayDir.localeCompare(right.runtimeGatewayDir))
}
async function selectRuntimeGateway() {
const generatedRuntimes = await findGeneratedBundleRuntimes(desktopOutputDir)
if (generatedRuntimes.length > 0) {
const selected = generatedRuntimes[0]
if (generatedRuntimes.length > 1) {
console.log(`Found ${generatedRuntimes.length} generated bundle runtimes; selecting first sorted path.`)
for (const runtime of generatedRuntimes) console.log(`- ${runtime.runtimeGatewayDir}`)
}
console.log(`Smoking packaged gateway runtime from ${selected.label}: ${selected.runtimeGatewayDir}`)
return selected.runtimeGatewayDir
}
if (process.env.OPENSQUILLA_REQUIRE_PACKAGED_GATEWAY_SMOKE === '1') {
throw new Error(`No current-platform generated Electron bundle runtime found under ${desktopOutputDir}.`)
}
console.log(`No current-platform generated Electron bundle runtime found under ${desktopOutputDir}; falling back to source runtime ${sourceRuntimeGatewayDir}.`)
return sourceRuntimeGatewayDir
}
function smokeEnv(tempHome, stateDir, config) {
const env = {}
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('OPENSQUILLA_')) continue
env[key] = value
}
return {
...env,
HOME: tempHome,
USERPROFILE: tempHome,
OPENSQUILLA_DESKTOP: '1',
OPENSQUILLA_INSTALL_METHOD: 'desktop',
OPENSQUILLA_STATE_DIR: stateDir,
OPENSQUILLA_GATEWAY_CONFIG_PATH: config,
PYTHONUNBUFFERED: '1',
PYTHONUTF8: '1',
PYTHONIOENCODING: 'utf-8:replace',
}
}
async function findFreePort() {
return await new Promise((resolvePort, reject) => {
const server = createServer()
server.unref()
server.once('error', reject)
server.listen(0, '127.0.0.1', () => {
const address = server.address()
if (!address || typeof address === 'string') {
server.close(() => reject(new Error('Could not determine an available loopback port.')))
return
}
const { port } = address
server.close((error) => {
if (error) reject(error)
else resolvePort(port)
})
})
})
}
async function sleep(ms) {
await new Promise((resolveSleep) => setTimeout(resolveSleep, ms))
}
function bodySnippet(body) {
return body.length > 300 ? `${body.slice(0, 300)}...` : body
}
async function healthCheck(url) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(1_000) })
const body = await response.text()
if (!response.ok) {
return { ok: false, detail: `${url} returned HTTP ${response.status}: ${bodySnippet(body)}` }
}
let payload
try {
payload = JSON.parse(body)
} catch (error) {
return {
ok: false,
detail: `${url} returned HTTP ${response.status} with non-JSON body: ${bodySnippet(body)} (${error instanceof Error ? error.message : String(error)})`,
}
}
if (payload?.ok === true) return { ok: true, detail: '' }
return { ok: false, detail: `${url} returned JSON without ok=true: ${bodySnippet(body)}` }
} catch (error) {
return { ok: false, detail: `${url} request failed: ${error instanceof Error ? error.message : String(error)}` }
}
}
async function fetchText(url) {
const response = await fetch(url, { signal: AbortSignal.timeout(2_000) })
const body = await response.text()
if (!response.ok) {
throw new Error(`${url} returned HTTP ${response.status}: ${bodySnippet(body)}`)
}
return body
}
function controlAssetUrls(html, baseUrl) {
const urls = []
for (const match of html.matchAll(/<script\b[^>]*\btype="module"[^>]*\bsrc="([^"]+)"/g)) {
urls.push(new URL(match[1], baseUrl).toString())
}
for (const match of html.matchAll(/<link\b[^>]*\brel="stylesheet"[^>]*\bhref="([^"]+)"/g)) {
urls.push(new URL(match[1], baseUrl).toString())
}
return urls
}
async function verifyControlUi(port, stdoutTail, stderrTail) {
const controlUrl = `http://127.0.0.1:${port}/control/`
const html = await fetchText(controlUrl)
const assetUrls = controlAssetUrls(html, controlUrl)
const hasModule = assetUrls.some((url) => url.includes('/static/dist/') && url.endsWith('.js'))
const hasStylesheet = assetUrls.some((url) => url.includes('/static/dist/') && url.endsWith('.css'))
if (!hasModule || !hasStylesheet) {
throw new Error(
`${controlUrl} did not inject Vite JS/CSS assets from /static/dist/. ` +
`Found assets: ${assetUrls.length > 0 ? assetUrls.join(', ') : '(none)'}.` +
formatTail(stdoutTail, stderrTail)
)
}
for (const url of assetUrls.filter((assetUrl) => assetUrl.includes('/static/dist/'))) {
await fetchText(url)
}
}
async function waitForGateway(port, childExit, stdoutTail, stderrTail) {
const deadline = Date.now() + deadlineMs
const healthzUrl = `http://127.0.0.1:${port}/healthz`
const healthUrl = `http://127.0.0.1:${port}/health`
let lastHealthFailure = ''
while (Date.now() < deadline) {
if (childExit.value) {
const { code, signal } = childExit.value
throw new Error(
`Gateway exited before becoming healthy (code=${code ?? 'null'} signal=${signal ?? 'null'}).` +
formatTail(stdoutTail, stderrTail)
)
}
const healthz = await healthCheck(healthzUrl)
if (healthz.ok) return
const health = await healthCheck(healthUrl)
if (health.ok) return
lastHealthFailure = `${healthz.detail}; ${health.detail}`
await sleep(pollIntervalMs)
}
const detail = lastHealthFailure ? ` Last health failure: ${lastHealthFailure}` : ''
throw new Error(`Timed out after ${deadlineMs / 1000}s waiting for ${healthzUrl} or ${healthUrl}.${detail}` + formatTail(stdoutTail, stderrTail))
}
async function terminateChild(child, childClosed) {
if (childClosed.value) return
await new Promise((resolveTerminate) => {
let settled = false
let forceTimer = null
let abandonTimer = null
function finish() {
if (settled) return
settled = true
if (forceTimer) clearTimeout(forceTimer)
if (abandonTimer) clearTimeout(abandonTimer)
resolveTerminate()
}
if (process.platform !== 'win32') child.once('close', finish)
if (child.exitCode === null && child.signalCode === null) {
if (process.platform === 'win32' && child.pid) {
const finishAfterTaskkill = () => {
if (childClosed.value || child.exitCode !== null || child.signalCode !== null) {
finish()
return
}
abandonTimer = setTimeout(finish, 1_000)
}
const killer = spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], {
stdio: 'ignore',
windowsHide: true,
})
const taskkillTimer = setTimeout(() => {
console.warn(`taskkill timed out while terminating gateway process ${child.pid}.`)
killer.kill()
finishAfterTaskkill()
}, killGraceMs)
killer.once('error', (error) => {
clearTimeout(taskkillTimer)
console.warn(`taskkill failed while terminating gateway process ${child.pid}: ${error.message}`)
finishAfterTaskkill()
})
killer.once('close', (code, signal) => {
clearTimeout(taskkillTimer)
if (code !== 0) {
console.warn(`taskkill exited while terminating gateway process ${child.pid} with code=${code ?? 'null'} signal=${signal ?? 'null'}.`)
}
finishAfterTaskkill()
})
} else if (process.platform === 'win32') {
console.warn('Gateway process had no PID for taskkill fallback.')
child.kill()
} else {
child.kill('SIGTERM')
}
}
forceTimer = setTimeout(() => {
if (child.exitCode === null && child.signalCode === null) {
if (process.platform !== 'win32') {
child.kill('SIGKILL')
abandonTimer = setTimeout(finish, 1_000)
} else {
finish()
}
} else {
finish()
}
}, killGraceMs)
})
}
async function main() {
const runtimeGatewayDir = await selectRuntimeGateway()
const candidates = gatewayBinaryCandidates(runtimeGatewayDir)
const gatewayBinary = findGatewayBinary(runtimeGatewayDir)
if (!gatewayBinary) {
throw new Error(
`Packaged gateway binary is missing. Checked: ${candidates.join(', ')}. Run npm run build:gateway first; release CI should run this after electron-builder.`
)
}
const tempHome = await mkdtemp(join(tmpdir(), 'opensquilla-gateway-smoke-'))
const config = join(tempHome, 'config.toml')
const stateDir = join(tempHome, 'state')
let child = null
const stdoutTail = []
const stderrTail = []
const childExit = { value: null }
const childClosed = { value: false }
try {
await mkdir(stateDir, { recursive: true })
await writeFile(
config,
[
'[auth]',
'mode = "none"',
'',
].join('\n'),
'utf8'
)
const port = await findFreePort()
child = spawn(gatewayBinary, ['gateway', 'run', '--port', String(port), '--bind', '127.0.0.1', '--config', config], {
cwd: dirname(gatewayBinary),
env: smokeEnv(tempHome, stateDir, config),
windowsHide: true,
})
child.stdout.on('data', (chunk) => {
stdoutTail.splice(0, stdoutTail.length, ...appendTail(stdoutTail, chunk))
})
child.stderr.on('data', (chunk) => {
stderrTail.splice(0, stderrTail.length, ...appendTail(stderrTail, chunk))
})
child.once('close', (code, signal) => {
childClosed.value = true
childExit.value = { code, signal }
})
child.once('error', (error) => {
childExit.value = { code: null, signal: `spawn error: ${error.message}` }
})
await waitForGateway(port, childExit, stdoutTail, stderrTail)
await verifyControlUi(port, stdoutTail, stderrTail)
console.log('OpenSquilla packaged gateway smoke passed.')
} finally {
if (child) await terminateChild(child, childClosed)
await rm(tempHome, { recursive: true, force: true })
}
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error))
process.exit(1)
})
@@ -0,0 +1,92 @@
import assert from 'node:assert/strict'
import { buildCliInvocation } from '../dist/cli-invocation.js'
// --- bundled posix: env pair + quoted binary, spaces survive quoting ---
{
const result = buildCliInvocation({
platform: 'darwin',
mode: 'bundled',
binaryPath: '/Applications/OpenSquilla.app/Contents/Resources/runtime/gateway/opensquilla-gateway/opensquilla-gateway',
// OPENSQUILLA_STATE_DIR is the OpenSquilla home root; runtime databases
// remain under the config-pinned <home>/state directory.
stateDir: '/opt/OpenSquilla Data',
configPath: '/opt/OpenSquilla Data/config.toml',
})
assert.equal(result.mode, 'bundled')
assert.equal(
result.prefix,
"OPENSQUILLA_STATE_DIR='/opt/OpenSquilla Data' "
+ "OPENSQUILLA_GATEWAY_CONFIG_PATH='/opt/OpenSquilla Data/config.toml' "
+ "'/Applications/OpenSquilla.app/Contents/Resources/runtime/gateway/opensquilla-gateway/opensquilla-gateway'",
)
}
// --- posix: single quotes inside paths get the '\'' escape ---
{
const result = buildCliInvocation({
platform: 'linux',
mode: 'bundled',
binaryPath: "/opt/o'brien apps/opensquilla-gateway",
stateDir: "/opt/o'brien data",
configPath: "/opt/o'brien data/config.toml",
})
assert.ok(result.prefix.includes("'/opt/o'\\''brien apps/opensquilla-gateway'"))
assert.ok(result.prefix.includes("OPENSQUILLA_STATE_DIR='/opt/o'\\''brien data'"))
}
// --- windows: PowerShell $env: syntax, '' doubling, & call operator ---
{
const result = buildCliInvocation({
platform: 'win32',
mode: 'bundled',
binaryPath: 'C:\\Program Files\\OpenSquilla\\resources\\runtime\\gateway\\opensquilla-gateway.exe',
stateDir: "C:\\Users\\o'brien\\AppData\\Roaming\\OpenSquilla\\opensquilla",
configPath: 'C:\\Users\\jo\\AppData\\Roaming\\OpenSquilla\\opensquilla\\config.toml',
})
assert.ok(result.prefix.startsWith("$env:OPENSQUILLA_STATE_DIR = 'C:\\Users\\o''brien\\AppData"))
assert.ok(result.prefix.includes("$env:OPENSQUILLA_GATEWAY_CONFIG_PATH = 'C:\\Users\\jo\\AppData"))
assert.ok(result.prefix.includes("& 'C:\\Program Files\\OpenSquilla\\resources\\runtime\\gateway\\opensquilla-gateway.exe'"))
}
// --- windows: unicode smart quotes are single-quote delimiters in PowerShell ---
{
const result = buildCliInvocation({
platform: 'win32',
mode: 'bundled',
binaryPath: 'C:\\Apps\\OpenSquilla\\opensquilla-gateway.exe',
stateDir: 'C:\\Users\\OBrien\\AppData\\Roaming\\OpenSquilla\\opensquilla',
configPath: 'C:\\Users\\OBrien\\AppData\\Roaming\\OpenSquilla\\opensquilla\\config.toml',
})
assert.ok(result.prefix.includes("$env:OPENSQUILLA_STATE_DIR = 'C:\\Users\\O’’Brien\\AppData"))
assert.ok(result.prefix.includes("$env:OPENSQUILLA_GATEWAY_CONFIG_PATH = 'C:\\Users\\O’’Brien\\AppData"))
}
// --- windows dev mode: PowerShell env syntax composes with the uv runner ---
{
const result = buildCliInvocation({
platform: 'win32',
mode: 'dev',
repoRoot: 'C:\\Dev Projects\\opensquilla',
stateDir: 'C:\\Users\\jo\\AppData\\Roaming\\OpenSquilla\\opensquilla',
configPath: 'C:\\Users\\jo\\AppData\\Roaming\\OpenSquilla\\opensquilla\\config.toml',
})
assert.equal(result.mode, 'dev')
assert.ok(result.prefix.startsWith("$env:OPENSQUILLA_STATE_DIR = 'C:\\Users\\jo\\AppData"))
assert.ok(result.prefix.endsWith("uv run --directory 'C:\\Dev Projects\\opensquilla' opensquilla"))
}
// --- dev mode: uv run with an explicit checkout directory, no cwd dependence ---
{
const result = buildCliInvocation({
platform: 'darwin',
mode: 'dev',
repoRoot: '/opt/dev projects/opensquilla',
stateDir: '/opt/OpenSquilla Data',
configPath: '/opt/OpenSquilla Data/config.toml',
})
assert.equal(result.mode, 'dev')
assert.ok(result.prefix.endsWith("uv run --directory '/opt/dev projects/opensquilla' opensquilla"))
}
console.log('cli-invocation: all assertions passed')
@@ -0,0 +1,135 @@
import assert from 'node:assert/strict'
import {
cleanupSelectorArgs,
DesktopCleanupPreviewStore,
desktopCleanupScopeIsContained,
parseDesktopCleanupMode,
parseDesktopCleanupProtocol,
sameDesktopCleanupScope,
} from '../dist/desktop-cleanup.js'
const report = parseDesktopCleanupProtocol({
schema_version: 1,
outcome: 'ready',
stable_code: 'cleanup_ready',
mode: 'delete-current-profile',
items: [{
kind: 'primary-home',
path: '/synthetic/user-data/opensquilla',
exists: true,
identity: '1:2',
}],
transaction_id: 'synthetic-transaction',
revision: 42,
scope_fingerprint: 'a'.repeat(64),
})
assert.equal(parseDesktopCleanupMode('reset-current-settings'), 'reset-current-settings')
assert.equal(parseDesktopCleanupMode('delete-current-profile'), 'delete-current-profile')
assert.equal(parseDesktopCleanupMode('delete-all-user-data'), 'delete-all-user-data')
assert.equal(parseDesktopCleanupMode('purge'), null)
assert.equal(report.items[0].identity, '1:2')
assert.equal(desktopCleanupScopeIsContained(report, '/synthetic/user-data'), true)
assert.equal(desktopCleanupScopeIsContained({
...report,
items: [{ ...report.items[0], path: '/synthetic/outside' }],
}, '/synthetic/user-data'), false)
assert.equal(desktopCleanupScopeIsContained({
...report,
items: [{ ...report.items[0], path: '/synthetic/user-data/..cache' }],
}, '/synthetic/user-data'), true)
assert.equal(sameDesktopCleanupScope(report, {
...report,
revision: 99,
items: report.items.map((item) => ({ ...item, identity: '3:4' })),
}, '/synthetic/user-data'), true, 'content metadata may change while the gateway stops')
assert.equal(sameDesktopCleanupScope(report, {
...report,
items: [...report.items, {
kind: 'new-user-data-entry',
path: '/synthetic/user-data/new-entry',
exists: true,
identity: '5:6',
}],
}, '/synthetic/user-data'), false, 'new paths require a new visible inventory')
assert.throws(
() => parseDesktopCleanupProtocol({ ...report, schema_version: 2 }),
/unsupported protocol schema/,
)
assert.throws(
() => parseDesktopCleanupProtocol({ ...report, revision: Number.MAX_SAFE_INTEGER + 1 }),
/invalid revision/,
)
assert.throws(
() => parseDesktopCleanupProtocol({ ...report, scope_fingerprint: 'not-a-digest' }),
/invalid scope fingerprint/,
)
assert.throws(
() => parseDesktopCleanupProtocol({ ...report, items: [{ path: '/unbound' }] }),
/invalid inventory item/,
)
const primarySelection = {
mode: 'delete-current-profile',
profileKind: 'primary',
recoveryId: null,
profileKey: 'primary',
}
assert.deepEqual(cleanupSelectorArgs(primarySelection), [
'--mode', 'delete-current-profile',
'--profile-kind', 'primary',
])
const recoverySelection = {
mode: 'reset-current-settings',
profileKind: 'recovery',
recoveryId: '01234567-89ab-4cde-8fab-0123456789ab',
profileKey: 'recovery:01234567-89ab-4cde-8fab-0123456789ab',
}
assert.deepEqual(cleanupSelectorArgs(recoverySelection), [
'--mode', 'reset-current-settings',
'--profile-kind', 'recovery',
'--recovery-id', recoverySelection.recoveryId,
])
const store = new DesktopCleanupPreviewStore(1_000)
const preview = store.create(report, primarySelection, 100)
assert.equal(
store.consume(preview.id, 'recovery:other', 200),
null,
'a preview must be bound to the exact active profile',
)
assert.equal(
store.consume(preview.id, 'primary', 200),
null,
'a rejected preview is consumed and cannot be replayed',
)
const fresh = store.create(report, primarySelection, 100)
assert.equal(store.consume(fresh.id, 'primary', 1_101), null, 'expired previews fail closed')
const discardable = store.create(report, primarySelection, 100)
assert.equal(store.discard(discardable.id, 'recovery:other'), false)
assert.equal(
store.consume(discardable.id, 'primary', 200)?.id,
discardable.id,
'a stale renderer cannot discard another profile preview',
)
const explicitlyDiscarded = store.create(report, primarySelection, 100)
assert.equal(store.discard(explicitlyDiscarded.id, 'primary'), true)
assert.equal(
store.consume(explicitlyDiscarded.id, 'primary', 200),
null,
'cancelled previews cannot be replayed',
)
const accepted = store.create(report, primarySelection, 100)
assert.equal(
store.consume(accepted.id, 'primary', 200)?.report.transaction_id,
'synthetic-transaction',
)
assert.equal(store.consume(accepted.id, 'primary', 201), null, 'previews are one-shot')
console.log('desktop cleanup contract checks passed')
@@ -0,0 +1,274 @@
import { strict as assert } from 'node:assert'
import {
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
realpath,
rm,
writeFile,
} from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { fileURLToPath } from 'node:url'
import { _electron as electron } from 'playwright'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '../..')
const RECOVERY_ID = '31234567-89ab-4cde-8fab-0123456789ab'
async function exists(path) {
try {
await lstat(path)
return true
} catch (error) {
if (error?.code === 'ENOENT') return false
throw error
}
}
async function waitFor(check, label, timeoutMs = 90_000) {
const startedAt = Date.now()
let lastError
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await check()
if (value) return value
} catch (error) {
lastError = error
}
await delay(250)
}
throw new Error(`Timed out waiting for ${label}: ${lastError?.message || lastError || ''}`)
}
function launchEnvironment(isolatedHome) {
const inherited = { ...process.env }
for (const name of Object.keys(inherited)) {
const upperName = name.toUpperCase()
if (
upperName.startsWith('OPENSQUILLA_')
|| ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY'].includes(upperName)
|| /(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH)/i.test(name)
) delete inherited[name]
}
return {
...inherited,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
OPENSQUILLA_DESKTOP_REPO_ROOT: repoRoot,
OPENSQUILLA_DESKTOP_SECRET_STORAGE: 'plain',
OPENSQUILLA_USER_STATE_DIR: join(isolatedHome, 'user-state'),
OPENSQUILLA_TEST_PROFILE_LOCK_ROOT: '1',
OPENSQUILLA_DESKTOP_GATEWAY_PORT: '18895',
OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE: '1',
OPENSQUILLA_OPENROUTER_LIVE_PRICING: '0',
OPENSQUILLA_GATEWAY_WORKSPACE_DIR: '',
OPENSQUILLA_WORKSPACE_DIR: '',
OPENSQUILLA_GATEWAY_STATE_DIR: '',
HTTP_PROXY: 'http://127.0.0.1:1',
HTTPS_PROXY: 'http://127.0.0.1:1',
ALL_PROXY: 'http://127.0.0.1:1',
NO_PROXY: '127.0.0.1,localhost',
}
}
const root = await realpath(await mkdtemp(join(tmpdir(), 'opensquilla-cleanup-e2e-')))
const userData = join(root, 'user-data')
const isolatedHome = join(root, 'home')
const primaryHome = join(userData, 'opensquilla')
const workspace = join(primaryHome, 'workspace')
const state = join(primaryHome, 'state')
const recoveryRoot = join(userData, 'recovery-profiles', RECOVERY_ID)
const recoveryHome = join(recoveryRoot, 'opensquilla')
const recoveryMarker = join(recoveryHome, 'keep-recovery-profile.txt')
const cleanupJournal = join(userData, '.opensquilla.profile-cleanup.json')
let desktopApp
try {
await mkdir(workspace, { recursive: true })
await mkdir(state, { recursive: true })
await mkdir(recoveryHome, { recursive: true })
await mkdir(isolatedHome, { recursive: true })
await writeFile(join(workspace, 'IDENTITY.md'), 'synthetic cleanup identity\n', 'utf8')
await writeFile(join(state, 'sessions.db.keep'), 'synthetic chat state\n', 'utf8')
await writeFile(recoveryMarker, 'must remain\n', 'utf8')
await writeFile(
join(primaryHome, 'config.toml'),
`workspace_dir = ${JSON.stringify(join(root, 'missing-workspace'))}\n`,
'utf8',
)
await writeFile(join(userData, 'desktop-locale'), 'en', 'utf8')
await writeFile(cleanupJournal, 'synthetic interrupted cleanup authority\n', 'utf8')
await writeFile(join(userData, 'desktop-profile-context.json'), `${JSON.stringify({
schema_version: 1,
active_profile_kind: 'primary',
active_recovery_id: null,
attention_acknowledgement: null,
updated_at: '2026-07-13T00:00:00.000Z',
}, null, 2)}\n`, 'utf8')
desktopApp = await electron.launch({
args: ['--use-mock-keychain', `--user-data-dir=${userData}`, packageRoot],
env: launchEnvironment(isolatedHome),
})
const page = await waitFor(async () => {
for (const candidate of desktopApp.windows()) {
if (candidate.isClosed()) continue
if (await candidate.locator('#recoveryPanel.visible').count().catch(() => 0)) {
return candidate
}
}
return null
}, 'cleanup test recovery page')
// Keep production confirmation intact; cancel the first click and accept the
// second so the real recovery-page button proves its busy state is cleared.
await desktopApp.evaluate(({ dialog }) => {
let cleanupDialogCount = 0
dialog.showMessageBox = async () => ({
response: cleanupDialogCount++ === 0 ? 0 : 1,
checkboxChecked: false,
})
})
const identityBeforeAbandon = await readFile(join(workspace, 'IDENTITY.md'))
const stateBeforeAbandon = await readFile(join(state, 'sessions.db.keep'))
const abandonButton = page.locator('#abandonCleanup')
await abandonButton.click()
await waitFor(async () => (
await abandonButton.isVisible() && await abandonButton.isEnabled()
), 'abandon button to recover after native-dialog cancellation')
assert.equal(await exists(cleanupJournal), true, 'cancel must preserve the cleanup journal')
await abandonButton.click()
await waitFor(async () => !await exists(cleanupJournal), 'cleanup journal abandonment')
await waitFor(async () => (
await page.locator('#recoveryCode').textContent() === 'effective_workspace_missing'
), 'post-abandon recovery inspection')
assert.equal(await page.locator('#cleanupAbandonGroup').isHidden(), true)
assert((await readdir(userData)).some((name) => (
name.startsWith('.opensquilla.profile-cleanup.abandoned.') && name.endsWith('.json')
)))
assert.deepEqual(await readFile(join(workspace, 'IDENTITY.md')), identityBeforeAbandon)
assert.deepEqual(await readFile(join(state, 'sessions.db.keep')), stateBeforeAbandon)
const preview = await page.evaluate(() => (
window.opensquillaDesktop.inspectDesktopCleanup({ mode: 'delete-current-profile' })
))
assert.equal(preview.ok, true)
assert.equal(preview.report.mode, 'delete-current-profile')
assert(preview.report.items.some((item) => (
item.kind === 'primary-home' && resolve(item.path) === resolve(primaryHome)
)), JSON.stringify(preview.report.items))
assert(preview.report.items.some((item) => item.kind === 'primary-logs'))
const process = desktopApp.process()
const exited = new Promise((resolveExit) => process.once('exit', resolveExit))
void page.evaluate((previewId) => (
window.opensquillaDesktop.applyDesktopCleanup({
previewId,
acknowledged: true,
confirmation: '',
})
), preview.previewId).catch(() => {})
await Promise.race([
exited,
delay(30_000).then(() => {
throw new Error('Desktop did not exit after deleting the current profile.')
}),
])
assert.equal(await exists(primaryHome), false)
assert.equal(await exists(join(userData, 'desktop-credential.json')), false)
assert.equal(await exists(join(userData, 'desktop-profile-context.json')), false)
assert.equal(await exists(join(userData, 'logs')), false, 'app.exit must not recreate desktop logs')
assert.equal(await readFile(recoveryMarker, 'utf8'), 'must remain\n')
// Recreate a synthetic primary profile, then verify delete-all is handed to
// the detached offline helper and does not begin until this second Electron
// process has actually exited.
desktopApp = null
await mkdir(workspace, { recursive: true })
await mkdir(state, { recursive: true })
await writeFile(join(workspace, 'IDENTITY.md'), 'synthetic delete-all identity\n', 'utf8')
await writeFile(
join(primaryHome, 'config.toml'),
`workspace_dir = ${JSON.stringify(join(root, 'still-missing-workspace'))}\n`,
'utf8',
)
await writeFile(join(userData, 'desktop-locale'), 'en', 'utf8')
await writeFile(join(userData, 'desktop-profile-context.json'), `${JSON.stringify({
schema_version: 1,
active_profile_kind: 'primary',
active_recovery_id: null,
attention_acknowledgement: null,
updated_at: '2026-07-13T00:00:01.000Z',
}, null, 2)}\n`, 'utf8')
await delay(200)
desktopApp = await electron.launch({
args: ['--use-mock-keychain', `--user-data-dir=${userData}`, packageRoot],
env: launchEnvironment(isolatedHome),
})
const deleteAllPage = await waitFor(async () => {
for (const candidate of desktopApp.windows()) {
if (candidate.isClosed()) continue
if (await candidate.locator('#recoveryPanel.visible').count().catch(() => 0)) {
return candidate
}
}
return null
}, 'delete-all test recovery page')
await desktopApp.evaluate(({ dialog }) => {
dialog.showMessageBox = async () => ({ response: 1, checkboxChecked: false })
})
const deleteAllPreview = await deleteAllPage.evaluate(() => (
window.opensquillaDesktop.inspectDesktopCleanup({ mode: 'delete-all-user-data' })
))
assert.equal(deleteAllPreview.ok, true)
assert.equal(await exists(primaryHome), true, 'inspection must remain read-only')
assert.equal(await exists(recoveryRoot), true, 'inspection must not touch recovery data')
const deleteAllProcess = desktopApp.process()
const deleteAllExited = new Promise((resolveExit) => deleteAllProcess.once('exit', resolveExit))
void deleteAllPage.evaluate((previewId) => (
window.opensquillaDesktop.applyDesktopCleanup({
previewId,
acknowledged: true,
confirmation: 'DELETE ALL OPENSQUILLA DATA',
})
), deleteAllPreview.previewId).catch(() => {})
await Promise.race([
deleteAllExited,
delay(30_000).then(() => {
throw new Error('Desktop did not exit before the delete-all helper handoff.')
}),
])
await waitFor(async () => (
!await exists(primaryHome)
&& !await exists(recoveryRoot)
&& !await exists(join(userData, 'logs'))
), 'post-exit delete-all helper completion', 30_000)
console.log(JSON.stringify({
ok: true,
trustedPreviewVerified: true,
postStopReinspectionVerified: true,
currentProfileDeleted: true,
recoveryProfilePreserved: true,
noLogWritebackAfterDelete: true,
abandonCleanupVerified: true,
deleteAllStartedAfterExit: true,
allProfilesDeletedByOfflineHelper: true,
}))
} catch (error) {
const desktopLog = await readFile(join(userData, 'logs', 'desktop.log'), 'utf8').catch(() => '')
if (desktopLog) console.error(desktopLog)
throw error
} finally {
await desktopApp?.close().catch(() => {})
await rm(root, { recursive: true, force: true }).catch(() => {})
}
@@ -0,0 +1,57 @@
import assert from 'node:assert/strict'
import { resolveLocaleFromTags } from '../dist/desktop-locale.js'
// --- preference order: first bundled match wins ---
// Regression: en-* is bundled and must match in place. Before the fix a Hong
// Kong system list [en-HK, zh-Hans-HK, zh-Hant-HK, fr-HK] resolved to 'fr' —
// the top two preferences both fell through and the fourth won.
assert.equal(resolveLocaleFromTags(['en-HK', 'zh-Hans-HK', 'zh-Hant-HK', 'fr-HK']), 'en')
assert.equal(resolveLocaleFromTags(['en', 'ja']), 'en')
assert.equal(resolveLocaleFromTags(['en_US']), 'en')
assert.equal(resolveLocaleFromTags(['ja-JP', 'en-US']), 'ja')
// --- Chinese: explicit script subtag wins over region ---
// Regression: zh-Hans with a Traditional-default region is still Simplified.
assert.equal(resolveLocaleFromTags(['zh-Hans-HK']), 'zh-Hans')
assert.equal(resolveLocaleFromTags(['zh-Hans-TW']), 'zh-Hans')
assert.equal(resolveLocaleFromTags(['zh-Hans-MO']), 'zh-Hans')
assert.equal(resolveLocaleFromTags(['zh-CN']), 'zh-Hans')
assert.equal(resolveLocaleFromTags(['zh']), 'zh-Hans')
assert.equal(resolveLocaleFromTags(['zh-Hans-CN']), 'zh-Hans')
assert.equal(resolveLocaleFromTags(['zh_Hans_HK']), 'zh-Hans')
// Traditional variants (explicit Hant, or bare Traditional-default regions)
// skip to the next preference rather than forcing Simplified text.
assert.equal(resolveLocaleFromTags(['zh-Hant']), 'en')
assert.equal(resolveLocaleFromTags(['zh-Hant-HK', 'ja-JP']), 'ja')
assert.equal(resolveLocaleFromTags(['zh-TW']), 'en')
assert.equal(resolveLocaleFromTags(['zh-HK', 'fr-FR']), 'fr')
assert.equal(resolveLocaleFromTags(['zh-MO', 'de']), 'de')
assert.equal(resolveLocaleFromTags(['zh_HK', 'fr-FR']), 'fr')
// Script detection is structural, never a substring match against extensions
// or private-use subtags.
assert.equal(resolveLocaleFromTags(['zh-Hant-x-hans', 'ja-JP']), 'ja')
assert.equal(resolveLocaleFromTags(['zh-CN-x-hant', 'fr-FR']), 'zh-Hans')
// --- other bundled languages and fallback ---
assert.equal(resolveLocaleFromTags(['fr-CA']), 'fr')
assert.equal(resolveLocaleFromTags(['fr_CA']), 'fr')
assert.equal(resolveLocaleFromTags(['de-AT']), 'de')
assert.equal(resolveLocaleFromTags(['es-419']), 'es')
// Unsupported languages skip to the next preference; nothing matches → 'en'.
assert.equal(resolveLocaleFromTags(['ko-KR', 'es-ES']), 'es')
assert.equal(resolveLocaleFromTags(['ko-KR', 'th-TH']), 'en')
assert.equal(resolveLocaleFromTags([]), 'en')
// Malformed entries (Electron APIs can surface non-strings) are skipped.
assert.equal(resolveLocaleFromTags([undefined, null, 42, 'ja']), 'ja')
assert.equal(resolveLocaleFromTags(['en--US', 'ja']), 'ja')
assert.equal(resolveLocaleFromTags(['zh-Hans-Hant', 'fr']), 'fr')
assert.equal(resolveLocaleFromTags(['zha', 'ja']), 'ja')
assert.equal(resolveLocaleFromTags(['zh!', 'de']), 'de')
console.log('desktop-locale resolution tests passed')
@@ -0,0 +1,227 @@
import assert from 'node:assert/strict'
import {
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
statSync,
symlinkSync,
writeFileSync,
} from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
allProfileContexts,
contextForProfile,
desktopProfileContextPath,
loadDesktopProfileContext,
persistDesktopProfileContextFile,
primaryProfilePaths,
profileKindEnvironment,
serializeDesktopProfileContext,
updateDesktopProfileContextFile,
} from '../dist/desktop-profile-context.js'
const root = mkdtempSync(join(tmpdir(), 'opensquilla-profile-context-'))
try {
const primary = primaryProfilePaths(root)
assert.equal(primary.home, join(root, 'opensquilla'))
assert.equal(primary.credentialPath, join(root, 'desktop-credential.json'))
assert.equal(primary.logsDir, join(root, 'logs'))
assert.equal(profileKindEnvironment('primary'), 'desktop-primary')
assert.equal(profileKindEnvironment('recovery'), 'desktop-recovery')
assert.equal(loadDesktopProfileContext(root).issue, null, 'a genuinely missing context is a fresh primary')
const recoveryId = '01234567-89ab-4cde-8fab-0123456789ab'
const recovery = contextForProfile(root, 'recovery', recoveryId)
mkdirSync(recovery.active.home, { recursive: true })
await persistDesktopProfileContextFile(root, recovery)
if (process.platform !== 'win32') {
assert.equal(
statSync(desktopProfileContextPath(root)).mode & 0o077,
0,
'context permissions must not grant group/other access',
)
}
assert.equal(
readdirSync(root).some((entry) => entry.includes('desktop-profile-context.json.') && entry.endsWith('.tmp')),
false,
'durable writes must not leave a temp file behind',
)
const loaded = loadDesktopProfileContext(root)
assert.equal(loaded.issue, null)
assert.equal(loaded.active.kind, 'recovery')
assert.equal(loaded.active.recoveryId, recoveryId)
assert.equal(loaded.active.home, join(root, 'recovery-profiles', recoveryId, 'opensquilla'))
assert.equal(loaded.active.credentialPath, join(root, 'recovery-profiles', recoveryId, 'desktop-credential.json'))
assert.equal(loaded.active.logsDir, join(root, 'recovery-profiles', recoveryId, 'logs'))
const profiles = allProfileContexts(root)
assert.deepEqual(profiles.map((profile) => profile.kind), ['primary', 'recovery'])
const concurrentRecoveryId = '31234567-89ab-4cde-8fab-0123456789ab'
mkdirSync(contextForProfile(root, 'recovery', concurrentRecoveryId).active.home, {
recursive: true,
})
const acknowledgement = {
stable_code: 'workspace_conflict',
candidates: [{
path: join(root, 'opensquilla', 'workspace'),
identity: '1:2',
modified_at_ns: 123,
}],
}
await Promise.all([
updateDesktopProfileContextFile(root, async (current) => {
await new Promise((resolve) => setTimeout(resolve, 20))
return contextForProfile(
root,
'recovery',
concurrentRecoveryId,
new Date().toISOString(),
current.persisted.attention_acknowledgement,
)
}),
updateDesktopProfileContextFile(root, (current) => contextForProfile(
root,
current.active.kind,
current.active.recoveryId,
new Date().toISOString(),
acknowledgement,
)),
])
const concurrentlyUpdated = loadDesktopProfileContext(root)
assert.equal(concurrentlyUpdated.active.recoveryId, concurrentRecoveryId)
assert.deepEqual(
concurrentlyUpdated.persisted.attention_acknowledgement,
acknowledgement,
'serialized read-modify-write must preserve both concurrent decisions',
)
let releaseLockedUpdate = () => {}
let markLockedUpdateStarted = () => {}
const lockedUpdateMayFinish = new Promise((resolve) => {
releaseLockedUpdate = resolve
})
const lockedUpdateStarted = new Promise((resolve) => {
markLockedUpdateStarted = resolve
})
const lockedUpdate = updateDesktopProfileContextFile(root, async (current) => {
markLockedUpdateStarted()
await lockedUpdateMayFinish
return contextForProfile(
root,
current.active.kind,
current.active.recoveryId,
new Date().toISOString(),
current.persisted.attention_acknowledgement,
)
})
await lockedUpdateStarted
let directPersistSettled = false
const directPersist = persistDesktopProfileContextFile(
root,
contextForProfile(root, 'primary'),
).then(() => {
directPersistSettled = true
})
await new Promise((resolve) => setTimeout(resolve, 50))
assert.equal(
directPersistSettled,
false,
'direct persistence must join the updater lock instead of publishing mid-transaction',
)
assert.equal(
loadDesktopProfileContext(root).active.recoveryId,
concurrentRecoveryId,
'a queued direct persistence must leave the updater snapshot untouched',
)
releaseLockedUpdate()
await lockedUpdate
await directPersist
assert.equal(loadDesktopProfileContext(root).active.kind, 'primary')
const externalChoice = contextForProfile(root, 'primary')
await assert.rejects(
updateDesktopProfileContextFile(root, async (current) => {
writeFileSync(
desktopProfileContextPath(root),
serializeDesktopProfileContext(externalChoice),
'utf8',
)
return contextForProfile(
root,
current.active.kind,
current.active.recoveryId,
new Date().toISOString(),
acknowledgement,
)
}),
/changed while it was being updated/,
)
assert.equal(
loadDesktopProfileContext(root).active.kind,
'primary',
'CAS rejection must preserve the external writer\'s newer choice',
)
const linkedId = '11234567-89ab-4cde-8fab-0123456789ab'
const outside = join(root, 'outside')
mkdirSync(outside)
const profileCountBeforeLinkedRoot = allProfileContexts(root).length
symlinkSync(outside, join(root, 'recovery-profiles', linkedId))
assert.equal(
allProfileContexts(root).length,
profileCountBeforeLinkedRoot,
'linked recovery roots must be ignored',
)
writeFileSync(desktopProfileContextPath(root), serializeDesktopProfileContext(
contextForProfile(root, 'recovery', linkedId),
), 'utf8')
assert.equal(
loadDesktopProfileContext(root).issue,
'desktop_selected_recovery_profile_unsafe',
'a selected linked recovery root must never be activated',
)
const corrupt = '{truncated'
writeFileSync(desktopProfileContextPath(root), corrupt, 'utf8')
const corruptLoaded = loadDesktopProfileContext(root)
assert.equal(corruptLoaded.active.kind, 'primary')
assert.equal(corruptLoaded.issue, 'desktop_profile_context_corrupt')
assert.equal(readFileSync(desktopProfileContextPath(root), 'utf8'), corrupt, 'inspection preserves corrupt context')
writeFileSync(desktopProfileContextPath(root), JSON.stringify({
schema_version: 2,
active_profile_kind: 'primary',
active_recovery_id: null,
attention_acknowledgement: null,
updated_at: new Date().toISOString(),
}), 'utf8')
assert.equal(
loadDesktopProfileContext(root).issue,
'desktop_profile_context_schema_too_new',
)
const missingId = '21234567-89ab-4cde-8fab-0123456789ab'
writeFileSync(desktopProfileContextPath(root), serializeDesktopProfileContext(
contextForProfile(root, 'recovery', missingId),
), 'utf8')
const missingLoaded = loadDesktopProfileContext(root)
assert.equal(missingLoaded.active.kind, 'recovery', 'the vanished selection is not silently changed')
assert.equal(missingLoaded.active.recoveryId, missingId)
assert.equal(missingLoaded.issue, 'desktop_selected_recovery_profile_missing')
// An explicit user choice is the only thing that replaces an invalid
// context. The durable write then clears the blocked selection state.
await persistDesktopProfileContextFile(root, contextForProfile(root, 'primary'))
assert.equal(loadDesktopProfileContext(root).issue, null)
assert.equal(loadDesktopProfileContext(root).active.kind, 'primary')
} finally {
rmSync(root, { recursive: true, force: true })
}
console.log('desktop profile context checks passed')
@@ -0,0 +1,82 @@
import assert from 'node:assert/strict'
import { DesktopContextLock } from '../dist/desktop-context-lock.js'
import { DesktopWriterAdmission } from '../dist/desktop-writer-admission.js'
const writers = new DesktopWriterAdmission()
const finishExistingWriter = writers.begin('existing recovery writer')
const lifecycleOwner = writers.close('apply downloaded update')
assert.equal(writers.closed, true)
assert.equal(writers.hasOwner(lifecycleOwner), true)
assert.throws(
() => writers.begin('late context writer'),
/writer admission is closed/,
)
let drained = false
const drain = writers.waitForAtMost(0).then(() => {
drained = true
})
await Promise.resolve()
assert.equal(drained, false, 'lifecycle operation must wait for the active writer')
finishExistingWriter()
finishExistingWriter()
await drain
assert.equal(writers.activeCount, 0, 'writer completion must be idempotent')
assert.equal(writers.reopen(Symbol('unrelated owner')), false)
assert.equal(writers.closed, true, 'an unrelated owner must not reopen admission')
assert.equal(writers.reopen(lifecycleOwner), true)
assert.equal(writers.closed, false)
assert.throws(() => writers.waitForAtMost(-1), /non-negative integer/)
const exclusive = writers.tryBeginExclusive('recovery selection')
assert(exclusive, 'exclusive admission must atomically close and reserve a writer')
assert.equal(writers.closed, true)
assert.equal(writers.activeCount, 1)
assert.equal(writers.tryBeginExclusive('second recovery selection'), null)
exclusive.finish()
writers.reopen(exclusive.admissionToken)
assert.equal(writers.closed, false)
assert.equal(writers.activeCount, 0)
const contextLock = new DesktopContextLock()
let releaseFirst = () => {}
let markFirstStarted = () => {}
const firstMayFinish = new Promise((resolve) => {
releaseFirst = resolve
})
const firstStarted = new Promise((resolve) => {
markFirstStarted = resolve
})
const order = []
const first = contextLock.runExclusive('profile-context', async () => {
order.push('first-start')
markFirstStarted()
await firstMayFinish
order.push('first-end')
})
const second = contextLock.runExclusive('profile-context', () => {
order.push('second')
})
await firstStarted
assert.deepEqual(order, ['first-start'], 'same-key operations must not overlap')
releaseFirst()
await Promise.all([first, second])
assert.deepEqual(order, ['first-start', 'first-end', 'second'])
await assert.rejects(
contextLock.runExclusive('reentrant', () => contextLock.runExclusive('reentrant', () => {})),
/cannot be re-entered/,
)
await assert.rejects(
contextLock.runExclusive('failure-recovery', () => {
throw new Error('synthetic failure')
}),
/synthetic failure/,
)
await contextLock.runExclusive('failure-recovery', () => {
order.push('recovered')
})
assert.equal(order.at(-1), 'recovered', 'a rejected operation must not poison the queue')
console.log('desktop profile substrate checks passed')
@@ -0,0 +1,190 @@
import { strict as assert } from 'node:assert'
import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { fileURLToPath } from 'node:url'
import { _electron as electron } from 'playwright'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '../..')
const mockVersion = process.env.OPENSQUILLA_DESKTOP_MOCK_UPDATE_VERSION || '99.0.0'
const relaunchLabels = ['Relaunch to Update', '重启以更新']
async function waitFor(check, label, timeoutMs = 45_000) {
const startedAt = Date.now()
let lastError
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await check()
if (value) return value
} catch (error) {
lastError = error
}
await delay(250)
}
const suffix = lastError ? ` Last error: ${lastError.message || lastError}` : ''
throw new Error(`Timed out waiting for ${label}.${suffix}`)
}
async function menuLabels(app) {
return await app.evaluate(({ Menu }) => {
const menu = Menu.getApplicationMenu()
const labels = []
function walk(items) {
for (const item of items || []) {
if (item.label) labels.push(item.label)
if (item.submenu) walk(item.submenu.items)
}
}
if (menu) walk(menu.items)
return labels
})
}
async function clickRelaunchToUpdate(app) {
return await app.evaluate(({ BrowserWindow, Menu }, labels) => {
const menu = Menu.getApplicationMenu()
function find(items) {
for (const item of items || []) {
if (item.label && labels.includes(item.label)) return item
const child = item.submenu ? find(item.submenu.items) : null
if (child) return child
}
return null
}
const item = menu ? find(menu.items) : null
if (!item || typeof item.click !== 'function') return false
item.click(undefined, BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], undefined)
return true
}, relaunchLabels)
}
const isolationRoot = await mkdtemp(join(tmpdir(), 'opensquilla-electron-mock-update-test-'))
const userDataDir = join(isolationRoot, 'chromium-user-data')
const isolatedHome = join(isolationRoot, 'home')
let app
try {
await mkdir(userDataDir, { recursive: true })
await mkdir(isolatedHome, { recursive: true })
// Seed a keyless, entirely synthetic profile so this update test reaches the
// Control UI without depending on a developer's real desktop credential.
const now = new Date().toISOString()
await writeFile(join(userDataDir, 'desktop-credential.json'), JSON.stringify({
provider: 'ollama',
model: 'opensquilla-update-test-model',
baseUrl: 'http://127.0.0.1:11434',
apiKeyEnv: '',
encryptedApiKey: '',
modelRoutingMode: 'direct',
routerMode: 'disabled',
routerDefaultTier: 'c1',
routerTiers: {},
searchProvider: 'duckduckgo',
searchApiKeyEnv: '',
encryptedSearchApiKey: '',
encryption: 'plain',
disableNetworkObservability: false,
createdAt: now,
updatedAt: now,
}, null, 2), { mode: 0o600 })
app = await electron.launch({
args: [
'--use-mock-keychain',
`--user-data-dir=${userDataDir}`,
packageRoot,
],
env: {
...process.env,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
OPENSQUILLA_DESKTOP_REPO_ROOT: repoRoot,
OPENSQUILLA_DESKTOP_SECRET_STORAGE: 'plain',
OPENSQUILLA_DESKTOP_MOCK_UPDATE_VERSION: mockVersion,
// mock install: OK. Availability/download now stay in renderer state.
OPENSQUILLA_DESKTOP_MOCK_UPDATE_DIALOG_RESPONSES: '0',
},
})
const runtimeIsolation = await app.evaluate(({ app: electronApp }) => ({
userData: electronApp.getPath('userData'),
home: process.env.HOME,
userProfile: process.env.USERPROFILE,
}))
assert.equal(await realpath(runtimeIsolation.userData), await realpath(userDataDir))
assert.equal(resolve(runtimeIsolation.home), resolve(isolatedHome))
assert.equal(resolve(runtimeIsolation.userProfile), resolve(isolatedHome))
const page = await app.firstWindow({ timeout: 60_000 })
await page.waitForLoadState('domcontentloaded', { timeout: 60_000 }).catch(() => {})
await waitFor(
async () => page.url().includes('/control/chat'),
'Control UI to load on Chat',
60_000,
)
const nativeAutoUpdateEnabled = await page.evaluate(
() => window.opensquillaDesktop.isAutoUpdateEnabled(),
)
assert.equal(nativeAutoUpdateEnabled, true, 'mock update should enable native update bridge')
const updateBannerCount = await page.locator('[data-testid="update-banner"]').count()
assert.equal(updateBannerCount, 0, 'desktop native update should suppress the web release banner')
const availableState = await waitFor(async () => {
return await page.evaluate(async () => {
const api = window.opensquillaDesktop
if (!api.getUpdateState) return null
const state = await api.getUpdateState()
return state?.status === 'available' ? state : null
})
}, 'mock update available renderer state')
assert.equal(availableState.latestVersion, mockVersion)
await page.locator('[data-testid="desktop-update-indicator"]').waitFor({ state: 'visible', timeout: 30_000 })
await page.locator('[data-testid="desktop-update-indicator"]').click()
await page.locator('[data-testid="desktop-update-download"]').click()
const downloadedState = await waitFor(async () => {
return await page.evaluate(async () => {
const state = await window.opensquillaDesktop.getUpdateState()
return state?.status === 'downloaded' ? state : null
})
}, 'mock update downloaded renderer state')
assert.equal(downloadedState.latestVersion, mockVersion)
const relaunchLabel = await waitFor(async () => {
const labels = await menuLabels(app)
return labels.find((label) => relaunchLabels.includes(label))
}, 'Relaunch to Update menu item')
assert.ok(relaunchLabel, 'pending mock update should expose relaunch menu item')
const clicked = await clickRelaunchToUpdate(app)
assert.equal(clicked, true, 'Relaunch to Update menu item should be clickable')
await delay(500)
assert.equal(page.isClosed(), false, 'mock install should not quit the app')
assert.match(await page.title(), /OpenSquilla/, 'Control UI should remain available after mock install')
const labelsAfterClick = await menuLabels(app)
assert.ok(
labelsAfterClick.some((label) => relaunchLabels.includes(label)),
'mock install keeps the pending relaunch menu available for repeated inspection',
)
console.log(JSON.stringify({
ok: true,
version: mockVersion,
updateState: downloadedState.status,
relaunchLabel,
url: page.url(),
title: await page.title(),
}, null, 2))
} finally {
await app?.close().catch(() => {})
await rm(isolationRoot, { recursive: true, force: true }).catch(() => {})
}
@@ -0,0 +1,278 @@
import { strict as assert } from 'node:assert'
import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { fileURLToPath } from 'node:url'
import { _electron as electron } from 'playwright'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '../..')
const screenshotPath = String(process.env.OPENSQUILLA_DESKTOP_ONBOARDING_SCREENSHOT || '').trim()
async function waitFor(check, label, timeoutMs = 60_000) {
const startedAt = Date.now()
let lastError
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await check()
if (value) return value
} catch (error) {
lastError = error
}
await delay(250)
}
const suffix = lastError ? ` Last error: ${lastError.message || lastError}` : ''
throw new Error(`Timed out waiting for ${label}.${suffix}`)
}
async function setupWindow(app) {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
const hasSetupForm = await page.locator('#setup-form').count().catch(() => 0)
if (hasSetupForm > 0) return page
}
return null
}, 'desktop onboarding window')
}
const userDataRoot = await mkdtemp(join(tmpdir(), 'opensquilla-electron-onboarding-test-'))
const userDataDir = join(userDataRoot, 'chromium-user-data')
const isolatedHome = join(userDataRoot, 'home')
await mkdir(isolatedHome, { recursive: true })
const app = await electron.launch({
args: [
'--use-mock-keychain',
`--user-data-dir=${userDataDir}`,
packageRoot,
],
env: {
...process.env,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
OPENSQUILLA_DESKTOP_REPO_ROOT: repoRoot,
OPENSQUILLA_DESKTOP_SECRET_STORAGE: 'plain',
OPENSQUILLA_DESKTOP_GATEWAY_PORT: '18897',
OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE: '1',
OPENSQUILLA_DESKTOP_MOCK_UPDATE_VERSION: '',
LANG: 'en_US.UTF-8',
LC_ALL: 'en_US.UTF-8',
},
})
try {
const page = await setupWindow(app)
await page.locator('#onboardingLocale').selectOption('zh-Hans')
assert.equal(await page.evaluate(() => document.documentElement.lang), 'zh-Hans')
assert.equal(await page.locator('[data-screen="0"] h2').innerText(), '选择设置深度')
assert.equal(await page.title(), '设置 OpenSquilla')
assert.doesNotMatch(await page.locator('[data-setup-mode="advanced"]').innerText(), /Smart Router mode/)
await page.locator('[data-setup-mode="advanced"]').click()
await page.locator('[data-screen="0"].active .next-button').click()
await page.locator('[data-screen="1"].active').waitFor({ state: 'visible', timeout: 10_000 })
assert.equal(await page.locator('[data-step-label="2"]').count(), 1, 'advanced setup should expose the routing-mode progress step')
assert.equal(await page.locator('#provider').inputValue(), 'tokenrhythm')
assert.equal(await page.locator('#baseUrl').inputValue(), 'https://tokenrhythm.studio/v1')
assert.equal(await page.locator('#model').inputValue(), 'deepseek-v4-pro')
assert.equal(await page.locator('#modelRoutingMode').inputValue(), 'squilla_router')
assert.equal(await page.locator('#routerMode').inputValue(), 'recommended')
const tokenRhythmFeature = page.locator('[data-provider-feature="tokenrhythm"]')
assert.equal(await tokenRhythmFeature.count(), 1)
assert.equal(await tokenRhythmFeature.locator('[data-tokenrhythm-title]').innerText(), '推荐使用 TokenRhythm')
assert.equal(
await tokenRhythmFeature.locator('[data-tokenrhythm-value]').innerText(),
'TokenRhythm API 调用限时免费。',
)
assert.equal(
await tokenRhythmFeature.locator('[data-tokenrhythm-registration]').innerText(),
'活动期间,注册并获取 API Key,即可免费调用 DeepSeek、GLM、MiniMax、Kimi 等主流模型。',
)
const tokenRhythmCta = tokenRhythmFeature.locator('#tokenrhythmRegister')
assert.equal(await tokenRhythmCta.innerText(), '注册并获取 API Key')
assert.equal(await tokenRhythmCta.getAttribute('href'), 'https://tokenrhythm.studio/register')
assert.equal(await tokenRhythmCta.getAttribute('target'), '_blank')
assert.equal(await tokenRhythmCta.getAttribute('rel'), 'noopener noreferrer')
assert.equal(
await tokenRhythmCta.getAttribute('aria-label'),
'注册并获取 API Key — TokenRhythm(在外部浏览器中打开)',
)
assert.equal(await tokenRhythmFeature.locator('img, svg, canvas').count(), 0)
assert.equal(await tokenRhythmFeature.locator('[data-provider="tokenrhythm"]').getAttribute('aria-pressed'), 'true')
const providerMoreToggle = page.locator('#providerMoreToggle')
const providerMorePanel = page.locator('#providerMorePanel')
assert.equal(await providerMoreToggle.getAttribute('aria-expanded'), 'false')
assert.equal(await providerMoreToggle.getAttribute('aria-controls'), 'providerMorePanel')
assert.equal(await providerMorePanel.isHidden(), true)
await page.locator('#onboardingLocale').selectOption('en')
assert.equal(await page.evaluate(() => document.documentElement.lang), 'en')
assert.equal(await page.locator('[data-screen="1"] h2').innerText(), 'Connect a provider')
assert.equal(await page.locator('#provider').inputValue(), 'tokenrhythm', 'locale changes should preserve the selected provider')
assert.equal(await tokenRhythmFeature.locator('[data-tokenrhythm-title]').innerText(), 'Recommended: TokenRhythm')
assert.equal(
await tokenRhythmFeature.locator('[data-tokenrhythm-value]').innerText(),
'TokenRhythm API calls are free for a limited time.',
)
assert.equal(
await tokenRhythmFeature.locator('[data-tokenrhythm-registration]').innerText(),
'During the promotion, register and get an API key to call DeepSeek, GLM, MiniMax, Kimi, and other leading models for free.',
)
assert.equal(await tokenRhythmCta.innerText(), 'Register and get an API key')
assert.equal(
await tokenRhythmCta.getAttribute('aria-label'),
'Register and get an API key — TokenRhythm (opens in external browser)',
)
await providerMoreToggle.click()
assert.equal(await providerMoreToggle.getAttribute('aria-expanded'), 'true')
assert.equal(await providerMorePanel.isVisible(), true)
const openRouterProvider = page.locator('#providerGrid [data-provider="openrouter"]')
await openRouterProvider.click()
assert.equal(await page.locator('#provider').inputValue(), 'openrouter')
assert.equal(await openRouterProvider.getAttribute('aria-pressed'), 'true')
assert.equal(await tokenRhythmFeature.locator('[data-provider="tokenrhythm"]').getAttribute('aria-pressed'), 'false')
await page.locator('#onboardingLocale').selectOption('zh-Hans')
assert.equal(await page.locator('#provider').inputValue(), 'openrouter', 'locale changes should preserve another provider selection')
await page.locator('#onboardingLocale').selectOption('en')
assert.equal(await page.locator('#provider').inputValue(), 'openrouter')
await tokenRhythmFeature.locator('[data-provider="tokenrhythm"]').click()
assert.equal(await page.locator('#provider').inputValue(), 'tokenrhythm', 'TokenRhythm should remain re-selectable')
assert.equal(await page.locator('#modelRoutingMode').inputValue(), 'squilla_router')
assert.equal(await tokenRhythmFeature.locator('[data-provider="tokenrhythm"]').getAttribute('aria-pressed'), 'true')
await page.locator('#onboardingLocale').selectOption('zh-Hans')
await page.locator('#apiKey').fill('synthetic-tokenrhythm-key')
await page.locator('[data-screen="1"].active .next-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 10_000 })
await page.waitForTimeout(300)
assert.equal(await page.locator('[data-screen="2"] h2').innerText(), '选择路由模式')
assert.equal(await page.locator('[data-model-routing-mode="squilla_router"]').isEnabled(), true)
assert.equal(await page.locator('[data-model-routing-mode="direct"]').isEnabled(), true)
assert.equal(await page.locator('[data-model-routing-mode="llm_ensemble"]').isEnabled(), true)
assert.equal(await page.locator('#modelRoutingMode').inputValue(), 'squilla_router')
assert.match(
await page.locator('[data-model-routing-mode="squilla_router"] small').innerText(),
/此提供商现有的 Squilla Router 层级默认值/,
)
assert.match(
await page.locator('[data-model-routing-mode="llm_ensemble"] small').innerText(),
/当前提供商的 static B5 Ensemble/,
)
if (screenshotPath) {
await mkdir(dirname(screenshotPath), { recursive: true })
await page.screenshot({ path: screenshotPath })
}
await page.locator('[data-screen="2"].active .next-button').click()
await page.locator('[data-screen="3"].active').waitFor({ state: 'visible', timeout: 10_000 })
assert.equal(await page.locator('[data-screen="3"] h2').innerText(), '候选模型池')
const tokenRhythmTierText = await page.locator('#tierBody').innerText()
for (const modelId of ['deepseek-v4-flash', 'deepseek-v4-pro', 'kimi-k2.7-code', 'glm-5.2', 'kimi-k2.6']) {
assert.match(tokenRhythmTierText, new RegExp(modelId.replaceAll('.', '\\.')))
}
await page.locator('[data-screen="3"].active .back-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 5_000 })
await page.locator('[data-model-routing-mode="direct"]').click()
assert.equal(await page.locator('#modelRoutingMode').inputValue(), 'direct')
assert.equal(await page.locator('#directModelRoute').inputValue(), 'deepseek-v4-pro')
await page.locator('#directModelRoute').fill('glm-5.2')
assert.equal(await page.locator('#model').inputValue(), 'glm-5.2')
await page.locator('[data-model-routing-mode="llm_ensemble"]').click()
assert.equal(await page.locator('#modelRoutingMode').inputValue(), 'llm_ensemble')
await page.locator('[data-screen="2"].active .next-button').click()
await page.locator('[data-screen="4"].active').waitFor({ state: 'visible', timeout: 10_000 })
await page.locator('[data-screen="4"].active .back-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 5_000 })
await page.locator('[data-screen="2"].active .back-button').click()
await page.locator('[data-screen="1"].active').waitFor({ state: 'visible', timeout: 5_000 })
await page.locator('#onboardingLocale').selectOption('en')
await page.locator('#providerGrid [data-provider="ollama"]').click()
assert.equal(await page.locator('#modelRoutingMode').inputValue(), 'direct')
assert.equal(await page.locator('#routerMode').inputValue(), 'disabled')
assert.equal(await page.locator('#model').inputValue(), '', 'direct-only providers without a default model should not inherit the previous provider model')
assert.equal(await page.locator('#endpointToggle').getAttribute('aria-expanded'), 'true', 'direct-only providers that need a model should open the endpoint panel')
await page.locator('[data-screen="1"].active .next-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 5_000 })
assert.equal(await page.locator('[data-model-routing-mode="squilla_router"]').isDisabled(), true)
assert.equal(await page.locator('[data-model-routing-mode="direct"]').isEnabled(), true)
assert.equal(await page.locator('[data-model-routing-mode="llm_ensemble"]').isDisabled(), true)
assert.equal(await page.locator('[data-step-label="3"]').isVisible(), false, 'route-excluded tier step should be hidden from the progress rail')
await page.locator('[data-screen="2"].active .next-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 5_000 })
assert.match(await page.locator('#error').innerText(), /Direct model is required/)
await page.locator('[data-screen="2"].active .back-button').click()
await page.locator('[data-screen="1"].active').waitFor({ state: 'visible', timeout: 5_000 })
await page.locator('#providerGrid [data-provider="openai"]').click()
assert.equal(await page.locator('#provider').inputValue(), 'openai')
assert.equal(await page.locator('#baseUrl').inputValue(), 'https://api.openai.com/v1')
assert.equal(await page.locator('#model').inputValue(), 'gpt-5.4-mini')
await page.locator('#providerGrid [data-provider="openai"].active').waitFor({ state: 'visible', timeout: 5_000 })
const openAiHint = await page.locator('#providerHint').innerText()
assert.match(openAiHint, /OpenAI-only tier profile/)
assert.doesNotMatch(openAiHint, /OPENAI_API_KEY/)
await page.locator('#apiKey').fill('test-openai-key')
await page.locator('[data-screen="1"].active .next-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 10_000 })
assert.equal(await page.locator('#modelRoutingMode').inputValue(), 'squilla_router')
assert.equal(await page.locator('[data-model-routing-mode="squilla_router"]').isEnabled(), true)
assert.equal(await page.locator('[data-model-routing-mode="direct"]').isEnabled(), true)
assert.equal(await page.locator('[data-model-routing-mode="llm_ensemble"]').isDisabled(), true)
await page.locator('[data-screen="2"].active .next-button').click()
await page.locator('[data-screen="3"].active').waitFor({ state: 'visible', timeout: 10_000 })
assert.match(await page.locator('[data-screen="3"] .eyebrow').innerText(), /step 04/i)
assert.equal(await page.locator('[data-screen="3"] h2').innerText(), 'Review tier models')
await page.locator('[data-screen="3"].active .back-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 5_000 })
await page.locator('[data-screen="2"].active .back-button').click()
await page.locator('[data-screen="1"].active').waitFor({ state: 'visible', timeout: 5_000 })
await tokenRhythmFeature.locator('[data-provider="tokenrhythm"]').click()
await page.locator('#apiKey').fill('synthetic-tokenrhythm-key')
await page.locator('[data-screen="1"].active .next-button').click()
await page.locator('[data-screen="2"].active').waitFor({ state: 'visible', timeout: 5_000 })
await page.locator('[data-model-routing-mode="llm_ensemble"]').click()
await page.locator('[data-screen="2"].active .next-button').click()
await page.locator('[data-screen="4"].active').waitFor({ state: 'visible', timeout: 5_000 })
await page.locator('#finish').click()
const saved = await waitFor(async () => {
const credential = JSON.parse(await readFile(join(userDataDir, 'desktop-credential.json'), 'utf8'))
if (credential.modelRoutingMode !== 'llm_ensemble') return null
const config = await readFile(join(userDataDir, 'opensquilla', 'config.toml'), 'utf8')
return { credential, config }
}, 'saved ensemble credential and config')
const { credential, config } = saved
assert.equal(credential.provider, 'tokenrhythm')
assert.equal(credential.modelRoutingMode, 'llm_ensemble')
assert.equal(credential.routerMode, 'recommended')
assert.equal(credential.routerTiers.c0.model, 'deepseek-v4-flash')
assert.equal(credential.routerTiers.c1.model, 'deepseek-v4-pro')
assert.equal(credential.routerTiers.c2.model, 'kimi-k2.7-code')
assert.equal(credential.routerTiers.c3.model, 'glm-5.2')
assert.equal(credential.routerTiers.image_model.model, 'kimi-k2.6')
assert.match(config, /\[squilla_router\]\nenabled = true/)
assert.doesNotMatch(config, /tier_profile = "tokenrhythm"/)
assert.match(config, /\[squilla_router\.tiers\.c0\]\nprovider = "tokenrhythm"\nmodel = "deepseek-v4-flash"/)
assert.match(config, /\[squilla_router\.tiers\.c3\]\nprovider = "tokenrhythm"\nmodel = "glm-5\.2"/)
assert.match(config, /\[llm_ensemble\]\nenabled = true\nselection_mode = "static_tokenrhythm_b5"/)
console.log(JSON.stringify({
ok: true,
provider: credential.provider,
modelRoutingMode: credential.modelRoutingMode,
routerMode: credential.routerMode,
model: credential.model,
screenshotPath: screenshotPath || null,
}, null, 2))
} finally {
await app.close().catch(() => {})
await rm(userDataRoot, { recursive: true, force: true }).catch(() => {})
}
@@ -0,0 +1,540 @@
import { strict as assert } from 'node:assert'
import { spawnSync } from 'node:child_process'
import { lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { fileURLToPath } from 'node:url'
import { _electron as electron } from 'playwright'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '../..')
const SOURCE_IDENTITY = '# Synthetic imported identity\n'
const TARGET_IDENTITY = '# Synthetic previous Desktop identity\n'
const SOURCE_CHAT = 'synthetic imported chat survives whole-profile transfer'
// A replace import performs two independently bounded receipt-verifier CLI
// calls (60 seconds each) around the mutating import. Windows CI cold starts
// can legitimately approach both bounds, so the E2E timeout must cover the
// product's advertised "few minutes" operation without becoming unbounded.
const PROFILE_IMPORT_APPLY_TIMEOUT_MS = 180_000
async function waitFor(check, label, timeoutMs = 90_000) {
const startedAt = Date.now()
let lastError
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await check()
if (value) return value
} catch (error) {
lastError = error
}
await delay(250)
}
throw new Error(`Timed out waiting for ${label}: ${lastError?.message || lastError || ''}`)
}
function runPython(source, args) {
const result = spawnSync('uv', ['run', 'python', '-c', source, ...args], {
cwd: repoRoot,
encoding: 'utf8',
env: { ...process.env, UV_CACHE_DIR: join(tmpdir(), 'opensquilla-profile-import-uv-cache') },
})
if (result.status !== 0) {
throw new Error(`Python fixture command failed: ${result.stderr || result.stdout}`)
}
return result.stdout.trim()
}
function seedProfile(home, identity, chat) {
runPython(`
import json, sqlite3, sys
from pathlib import Path
home = Path(sys.argv[1]).resolve()
identity = sys.argv[2]
chat = sys.argv[3]
workspace = home / "workspace"
state = home / "state"
workspace.mkdir(parents=True, exist_ok=True)
state.mkdir(parents=True, exist_ok=True)
for name, value in {
"IDENTITY.md": identity,
"USER.md": "# Synthetic user\\n",
"SOUL.md": "# Synthetic soul\\n",
"MEMORY.md": "# Synthetic memory\\n",
}.items():
(workspace / name).write_text(value, encoding="utf-8", newline="")
(home / "config.toml").write_text(
"workspace_dir = " + json.dumps(str(workspace)) + "\\n"
+ "state_dir = " + json.dumps(str(state)) + "\\n"
+ "[llm]\\nprovider = \\"ollama\\"\\nmodel = \\"synthetic-import-model\\"\\n"
+ "base_url = \\"http://127.0.0.1:11434/v1\\"\\napi_key_env = \\"\\"\\n",
encoding="utf-8",
newline="",
)
with sqlite3.connect(state / "sessions.db") as connection:
connection.execute("CREATE TABLE synthetic_import_chat (id TEXT PRIMARY KEY, body TEXT NOT NULL)")
connection.execute("INSERT INTO synthetic_import_chat VALUES (?, ?)", ("session-1", chat))
assert connection.execute("PRAGMA quick_check").fetchone() == ("ok",)
`, [home, identity, chat])
}
async function writeProviderProfileConfig(home, settings) {
const workspace = join(home, 'workspace')
const state = join(home, 'state')
const lines = [
`workspace_dir = ${JSON.stringify(workspace)}`,
`state_dir = ${JSON.stringify(state)}`,
`search_provider = ${JSON.stringify(settings.searchProvider || 'duckduckgo')}`,
`search_api_key_env = ${JSON.stringify(settings.searchApiKeyEnv || '')}`,
'',
'[llm]',
`provider = ${JSON.stringify(settings.provider)}`,
`model = ${JSON.stringify(settings.model)}`,
`base_url = ${JSON.stringify(settings.baseUrl)}`,
`api_key_env = ${JSON.stringify(settings.apiKeyEnv || '')}`,
'',
'[squilla_router]',
`enabled = ${settings.routerEnabled === true ? 'true' : 'false'}`,
'default_tier = "c2"',
'confidence_threshold = 0.77',
'',
'[squilla_router.tiers.c0]',
`provider = ${JSON.stringify(settings.provider)}`,
'model = "synthetic-source-tier-model"',
'',
'[llm_ensemble]',
'enabled = false',
'selection_mode = "static_openrouter_b5"',
'',
'[privacy]',
`disable_network_observability = ${settings.disableNetworkObservability ? 'true' : 'false'}`,
'',
'[control_ui]',
'enabled = true',
'base_path = "/control"',
'',
]
await writeFile(join(home, 'config.toml'), lines.join('\n'), 'utf8')
}
async function seedDesktopCredential(userData, settings) {
await mkdir(userData, { recursive: true })
const now = '2026-07-12T00:00:00.000Z'
const credential = {
provider: settings.provider,
model: settings.model,
baseUrl: settings.baseUrl,
apiKeyEnv: settings.apiKeyEnv || '',
encryptedApiKey: settings.apiKey
? Buffer.from(settings.apiKey, 'utf8').toString('base64')
: '',
encryption: 'plain',
configAuthority: 'generated',
importTransactionId: '',
createdAt: now,
updatedAt: now,
}
const raw = `${JSON.stringify(credential, null, 2)}\n`
await writeFile(join(userData, 'desktop-credential.json'), raw, { mode: 0o600 })
return raw
}
async function snapshotTree(root) {
const result = {}
async function visit(path, relative = '') {
const info = await lstat(path)
assert.equal(info.isSymbolicLink(), false, `fixture cannot contain symlinks: ${path}`)
if (info.isDirectory()) {
result[`${relative || '.'}/`] = { type: 'directory', mode: info.mode }
for (const name of (await readdir(path)).sort()) {
await visit(join(path, name), relative ? `${relative}/${name}` : name)
}
return
}
assert.equal(info.isFile(), true)
result[relative] = {
type: 'file',
mode: info.mode,
bytes: (await readFile(path)).toString('base64'),
}
}
await visit(root)
return result
}
function launchEnvironment(isolatedHome, port) {
const inherited = { ...process.env }
for (const name of Object.keys(inherited)) {
if (name === 'DISPLAY' || name === 'XAUTHORITY') continue
const upperName = name.toUpperCase()
if (
name.startsWith('OPENSQUILLA_')
|| ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY'].includes(upperName)
|| /(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH)/i.test(name)
|| /^(?:AWS|AZURE|GOOGLE|ANTHROPIC|OPENAI|OPENROUTER|MINIMAX|DEEPSEEK|GROQ|MISTRAL|COHERE|GEMINI|OLLAMA|XAI|MOONSHOT|DASHSCOPE|SILICONFLOW|ZHIPU|BAIDU|VOLCENGINE|TENCENT|ALIYUN|HF|HUGGINGFACE)_/i.test(name)
) delete inherited[name]
}
return {
...inherited,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
OPENSQUILLA_DESKTOP_REPO_ROOT: repoRoot,
OPENSQUILLA_DESKTOP_SECRET_STORAGE: 'plain',
OPENSQUILLA_USER_STATE_DIR: join(isolatedHome, 'user-state'),
OPENSQUILLA_TEST_PROFILE_LOCK_ROOT: '1',
OPENSQUILLA_DESKTOP_GATEWAY_PORT: String(port),
OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE: '1',
OPENSQUILLA_OPENROUTER_LIVE_PRICING: '0',
UV_CACHE_DIR: join(isolatedHome, '.uv-cache'),
HTTP_PROXY: 'http://127.0.0.1:1',
HTTPS_PROXY: 'http://127.0.0.1:1',
ALL_PROXY: 'http://127.0.0.1:1',
NO_PROXY: '127.0.0.1,localhost',
http_proxy: 'http://127.0.0.1:1',
https_proxy: 'http://127.0.0.1:1',
all_proxy: 'http://127.0.0.1:1',
no_proxy: '127.0.0.1,localhost',
LANG: 'en_US.UTF-8',
LC_ALL: 'en_US.UTF-8',
}
}
async function launchDesktop(userData, isolatedHome, port) {
return await electron.launch({
args: ['--use-mock-keychain', `--user-data-dir=${userData}`, packageRoot],
env: launchEnvironment(isolatedHome, port),
})
}
async function onboardingPage(app) {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
if (await page.locator('#setup-form').count().catch(() => 0)) return page
}
return null
}, 'Desktop onboarding')
}
async function recoveryPage(app) {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
if (await page.locator('#recoveryPanel.visible').count().catch(() => 0)) return page
}
return null
}, 'recovery profile confirmation page')
}
async function controlPage(app) {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
let pathname = ''
try { pathname = new URL(page.url()).pathname } catch { pathname = '' }
if (!['/control/chat', '/control/chat/new'].includes(pathname)) continue
if (await page.locator('.chat-textarea').count().catch(() => 0)) return page
}
return null
}, 'Desktop Control UI', 120_000)
}
const root = await realpath(await mkdtemp(join(tmpdir(), 'opensquilla-profile-import-e2e-')))
let app = null
try {
// A single detected CLI profile remains unselected, and skipping performs no import.
const skipHome = join(root, 'skip-home')
const skipSource = join(skipHome, '.opensquilla')
const skipDesktopSource = join(root, 'skip-desktop-source')
const skipUserData = join(root, 'skip-user-data')
seedProfile(skipSource, SOURCE_IDENTITY, SOURCE_CHAT)
seedProfile(skipDesktopSource, '# Synthetic alternate Desktop identity\n', 'alternate chat')
const skipSourceBefore = await snapshotTree(skipSource)
const skipDesktopSourceBefore = await snapshotTree(skipDesktopSource)
app = await launchDesktop(skipUserData, skipHome, 18921)
let page = await onboardingPage(app)
await page.locator('[data-screen="5"].active').waitFor({ state: 'visible' })
assert.equal(await page.locator('#migrationSource').inputValue(), '')
assert.equal(await page.locator('#migrationSource option').count(), 2)
assert.equal(await page.locator('#migrationPreview').isDisabled(), true)
await app.evaluate(({ dialog }, selectedPath) => {
dialog.showOpenDialog = async () => ({ canceled: false, filePaths: [selectedPath] })
}, skipDesktopSource)
await page.locator('#migrationSourceKind').selectOption('desktop-home')
await page.locator('#migrationBrowse').click()
await waitFor(async () => (
await page.locator('#migrationSource option').count() === 3
), 'second explicitly browsed candidate')
assert.equal(await page.locator('#migrationSource').inputValue(), skipDesktopSource)
await page.locator('#migrationSource').selectOption('')
assert.equal(await page.locator('#migrationPreview').isDisabled(), true)
await page.locator('#migrationSkip').click()
await page.locator('[data-screen="0"].active').waitFor({ state: 'visible' })
assert.deepEqual(await snapshotTree(skipSource), skipSourceBefore)
assert.deepEqual(await snapshotTree(skipDesktopSource), skipDesktopSourceBefore)
assert.notEqual(
await readFile(join(skipUserData, 'opensquilla', 'workspace', 'IDENTITY.md'), 'utf8').catch(() => ''),
SOURCE_IDENTITY,
)
await app.close()
app = null
// A non-empty Desktop target is backed up and replaced as one profile; source stays read-only.
const importHome = join(root, 'import-home')
const source = join(importHome, '.opensquilla')
const userData = join(root, 'import-user-data')
const target = join(userData, 'opensquilla')
seedProfile(source, SOURCE_IDENTITY, SOURCE_CHAT)
seedProfile(target, TARGET_IDENTITY, 'synthetic previous Desktop chat')
const sourceBefore = await snapshotTree(source)
app = await launchDesktop(userData, importHome, 18922)
page = await onboardingPage(app)
await page.locator('[data-screen="5"].active').waitFor({ state: 'visible' })
assert.equal(await page.locator('#migrationSource').inputValue(), '')
await page.locator('#migrationSource').selectOption(source)
await waitFor(async () => !(await page.locator('#migrationPreview').isDisabled()), 'explicit source selection')
await page.locator('#migrationPreview').click()
try {
await waitFor(async () => !(await page.locator('#migrationImport').isDisabled()), 'whole-replace preview')
} catch (error) {
const diagnostics = await page.evaluate(() => ({
error: document.getElementById('error')?.textContent || '',
summary: document.getElementById('migrationSummary')?.textContent || '',
source: document.getElementById('migrationSource')?.value || '',
}))
throw new Error(`${error.message}; diagnostics=${JSON.stringify(diagnostics)}`)
}
await app.evaluate(({ dialog }) => {
dialog.showMessageBox = async () => ({ response: 1, checkboxChecked: false })
})
await page.locator('#migrationImport').click()
try {
await page.locator('#migrationDoneNote').waitFor({
state: 'visible',
timeout: PROFILE_IMPORT_APPLY_TIMEOUT_MS,
})
} catch (error) {
const renderer = await page.evaluate(() => ({
error: document.getElementById('error')?.textContent || '',
statusVisible: !document.getElementById('migrationStatus')?.hidden,
summaryVisible: !document.getElementById('migrationSummary')?.hidden,
})).catch(() => ({ error: '', statusVisible: false, summaryVisible: false }))
const pendingPhase = await readFile(
join(userData, 'migration-provider-setup.json'),
'utf8',
).then((raw) => JSON.parse(raw)?.phase || '').catch(() => '')
const receiptCount = await readdir(join(target, 'migration', 'opensquilla'))
.then((entries) => entries.length)
.catch(() => 0)
const backupCount = await readdir(userData)
.then((entries) => entries.filter((name) => name.startsWith('opensquilla.backup.')).length)
.catch(() => 0)
const migrationResult = await readFile(
join(userData, 'migration-last-result.json'),
'utf8',
).then((raw) => {
const value = JSON.parse(raw)
return {
ok: value?.ok === true,
migrationApplied: value?.migrationApplied === true,
restartOk: value?.restartOk === true,
requiresProviderSetup: value?.requiresProviderSetup === true,
detail: typeof value?.detail === 'string' ? value.detail : '',
}
}).catch(() => null)
const diagnostics = {
renderer,
pendingPhase,
receiptCount,
backupCount,
migrationResult,
importedIdentityPresent: await readFile(
join(target, 'workspace', 'IDENTITY.md'),
'utf8',
).then((value) => value === SOURCE_IDENTITY).catch(() => false),
}
const reportDir = process.env.CI_REPORT_DIR
if (reportDir) {
await mkdir(reportDir, { recursive: true })
await writeFile(
join(reportDir, 'profile-import-timeout.json'),
`${JSON.stringify(diagnostics, null, 2)}\n`,
'utf8',
)
}
throw new Error(`${error.message}; diagnostics=${JSON.stringify(diagnostics)}`)
}
assert.match(await page.locator('#migrationDoneNote').innerText(), /Import complete/i)
assert.deepEqual(await snapshotTree(source), sourceBefore, 'source bytes and permissions changed')
assert.equal(await readFile(join(source, '.opensquilla-imported.json'), 'utf8').catch(() => null), null)
assert.equal(await readFile(join(target, 'workspace', 'IDENTITY.md'), 'utf8'), SOURCE_IDENTITY)
const importedChat = runPython(`
import sqlite3, sys
with sqlite3.connect('file:' + sys.argv[1] + '?mode=ro', uri=True) as connection:
print(connection.execute('SELECT body FROM synthetic_import_chat WHERE id = ?', ('session-1',)).fetchone()[0])
`, [join(target, 'state', 'sessions.db')])
assert.equal(importedChat, SOURCE_CHAT)
const backups = (await readdir(userData)).filter((name) => name.startsWith('opensquilla.backup.'))
assert.equal(backups.length, 1)
assert.equal(
await readFile(join(userData, backups[0], 'workspace', 'IDENTITY.md'), 'utf8'),
TARGET_IDENTITY,
)
await app.close()
app = null
// Settings import with a required key must release exclusive admission before
// onboarding, preserve source config bytes, and retain the previous credential.
const settingsHome = join(root, 'settings-home')
const settingsSource = join(settingsHome, '.opensquilla')
const settingsUserData = join(root, 'settings-user-data')
const settingsTarget = join(settingsUserData, 'opensquilla')
seedProfile(settingsSource, SOURCE_IDENTITY, SOURCE_CHAT)
seedProfile(settingsTarget, TARGET_IDENTITY, 'synthetic previous settings chat')
await writeProviderProfileConfig(settingsSource, {
provider: 'openai',
model: 'gpt-5.4-mini',
baseUrl: 'https://api.openai.com/v1',
apiKeyEnv: 'OPENAI_API_KEY',
searchProvider: 'brave',
searchApiKeyEnv: 'BRAVE_API_KEY',
routerEnabled: false,
disableNetworkObservability: true,
})
const importedEnvBytes = Buffer.from(
'OPENAI_API_KEY="synthetic-source-env-key"\r\nTRAILING_VALUE=keep\r\n\r\n',
)
await writeFile(join(settingsSource, '.env'), importedEnvBytes)
await writeProviderProfileConfig(settingsTarget, {
provider: 'openai',
model: 'synthetic-old-target-model',
baseUrl: 'https://api.openai.com/v1',
apiKeyEnv: 'OPENAI_API_KEY',
routerEnabled: true,
disableNetworkObservability: false,
})
const oldCredential = await seedDesktopCredential(settingsUserData, {
provider: 'openai',
model: 'synthetic-old-target-model',
baseUrl: 'https://api.openai.com/v1',
apiKeyEnv: 'OPENAI_API_KEY',
apiKey: 'synthetic-old-target-key',
})
app = await launchDesktop(settingsUserData, settingsHome, 18924)
const settingsControl = await controlPage(app)
const settingsPreview = await settingsControl.evaluate(async (sourcePath) => (
await window.opensquillaDesktop.migrationSummary({ source: sourcePath })
), settingsSource)
assert.equal(settingsPreview.ok, false, JSON.stringify(settingsPreview))
assert.equal(typeof settingsPreview.previewId, 'string')
assert.equal(
settingsPreview.report.items.filter((item) => item.status === 'error').at(0)?.kind,
'preflight/target',
)
await app.evaluate(({ dialog }) => {
dialog.showMessageBox = async () => ({ response: 1, checkboxChecked: false })
})
await settingsControl.evaluate(({ previewId }) => {
void window.opensquillaDesktop.migrationRun({ previewId, overwrite: true })
return true
}, { previewId: settingsPreview.previewId })
const requiredKeyOnboarding = await onboardingPage(app)
await requiredKeyOnboarding.locator('[data-screen="0"].active').waitFor({
state: 'visible',
timeout: 90_000,
})
await requiredKeyOnboarding.locator('[data-screen="0"].active .next-button').click()
await requiredKeyOnboarding.locator('[data-screen="1"].active').waitFor({
state: 'visible',
timeout: 90_000,
})
assert.equal(await requiredKeyOnboarding.locator('#provider').inputValue(), 'openai')
assert.equal(await requiredKeyOnboarding.locator('#model').inputValue(), 'gpt-5.4-mini')
const importedConfigBeforeCredential = await readFile(join(settingsTarget, 'config.toml'))
assert.match(importedConfigBeforeCredential.toString('utf8'), /search_provider = "brave"/)
assert.match(importedConfigBeforeCredential.toString('utf8'), /confidence_threshold = 0\.77/)
assert.match(
importedConfigBeforeCredential.toString('utf8'),
/disable_network_observability = true/,
)
await requiredKeyOnboarding.locator('#apiKey').fill('synthetic-new-imported-key')
await requiredKeyOnboarding.locator('[data-screen="1"].active .next-button').click()
await requiredKeyOnboarding.locator('[data-screen="4"].active').waitFor({ state: 'visible' })
await requiredKeyOnboarding.locator('#finish').click()
const adopted = await waitFor(async () => {
const pending = await readFile(
join(settingsUserData, 'migration-provider-setup.json'),
'utf8',
).catch(() => null)
if (pending !== null) return null
const raw = await readFile(join(settingsUserData, 'desktop-credential.json'), 'utf8')
const credential = JSON.parse(raw)
return credential.configAuthority === 'profile' ? credential : null
}, 'required-key imported credential adoption')
assert.match(adopted.importTransactionId, /^[0-9a-f-]{36}$/i)
assert.equal(adopted.model, 'gpt-5.4-mini')
assert.equal(
Buffer.from(adopted.encryptedApiKey, 'base64').toString('utf8'),
'synthetic-new-imported-key',
)
assert.deepEqual(
await readFile(join(settingsTarget, 'config.toml')),
importedConfigBeforeCredential,
'provider adoption rewrote imported config.toml',
)
assert.deepEqual(
await readFile(join(settingsTarget, '.env')),
importedEnvBytes,
'provider adoption rewrote imported .env bytes',
)
const credentialBackup = join(
settingsUserData,
`desktop-credential.import-backup.${adopted.importTransactionId}.json`,
)
assert.equal(await readFile(credentialBackup, 'utf8'), oldCredential)
if (process.platform !== 'win32') {
assert.equal((await lstat(credentialBackup)).mode & 0o777, 0o600)
}
await app.close()
app = null
// A selected recovery H can use the app, but it cannot import another profile.
const recoveryHome = join(root, 'recovery-home')
const recoveryUserData = join(root, 'recovery-user-data')
const recoveryId = '12345678-1234-4234-8234-123456789abc'
await mkdir(join(recoveryUserData, 'recovery-profiles', recoveryId, 'opensquilla'), { recursive: true })
await writeFile(join(recoveryUserData, 'desktop-profile-context.json'), JSON.stringify({
schema_version: 1,
active_profile_kind: 'recovery',
active_recovery_id: recoveryId,
attention_acknowledgement: null,
updated_at: new Date().toISOString(),
}, null, 2))
app = await launchDesktop(recoveryUserData, recoveryHome, 18923)
page = await recoveryPage(app)
const rejected = await page.evaluate(() => window.opensquillaDesktop.migrationSummary())
assert.equal(rejected.ok, false)
assert.match(rejected.raw, /primary profile/i)
console.log(JSON.stringify({
explicitSelectionAndSkip: true,
multipleCandidates: true,
wholeReplacement: true,
sourceUnchanged: true,
identityAndChatImported: true,
settingsRequiredKeyCompleted: true,
importedConfigPreserved: true,
previousCredentialBackedUp: true,
recoveryProfileRejected: true,
}, null, 2))
} finally {
if (app) await app.close().catch(() => {})
await rm(root, { recursive: true, force: true })
}
@@ -0,0 +1,524 @@
import { strict as assert } from 'node:assert'
import {
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
realpath,
rm,
writeFile,
} from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { fileURLToPath } from 'node:url'
import { _electron as electron } from 'playwright'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '../..')
const RECOVERY_ID = '21234567-89ab-4cde-8fab-0123456789ab'
const observedRendererPages = new WeakSet()
const rendererDiagnostics = []
const LOCALES = {
en: {
title: 'Starting OpenSquilla',
bootAria: 'OpenSquilla startup',
recoveryTitle: 'Your primary profile needs recovery',
continueRecovery: 'Continue recovery profile',
createRecovery: 'Create recovery profile',
retryPrimary: 'Retry primary profile',
cleanupRecoveryTitle: 'A data cleanup stopped partway through',
abandonCleanup: 'Preserve remaining data and continue',
},
'zh-Hans': {
title: '正在启动 OpenSquilla',
bootAria: 'OpenSquilla 启动',
recoveryTitle: '主配置需要恢复',
continueRecovery: '继续恢复配置',
createRecovery: '创建恢复配置',
retryPrimary: '重试主配置',
cleanupRecoveryTitle: '数据清理在中途停止',
abandonCleanup: '保留剩余数据并继续',
},
ja: {
title: 'OpenSquilla を起動しています',
bootAria: 'OpenSquilla の起動',
recoveryTitle: 'プライマリプロファイルの復旧が必要です',
continueRecovery: '復旧プロファイルを続行',
createRecovery: '復旧プロファイルを作成',
retryPrimary: 'プライマリを再試行',
cleanupRecoveryTitle: 'データのクリーンアップが途中で停止しました',
abandonCleanup: '残りのデータを保持して続行',
},
fr: {
title: "Démarrage d'OpenSquilla",
bootAria: "Démarrage d'OpenSquilla",
recoveryTitle: 'Le profil principal doit être récupéré',
continueRecovery: 'Continuer ce profil',
createRecovery: 'Créer un profil de récupération',
retryPrimary: 'Réessayer le profil principal',
cleanupRecoveryTitle: 'Un nettoyage des données sest arrêté en cours de route',
abandonCleanup: 'Conserver les données restantes et continuer',
},
de: {
title: 'OpenSquilla wird gestartet',
bootAria: 'OpenSquilla-Start',
recoveryTitle: 'Das Hauptprofil muss wiederhergestellt werden',
continueRecovery: 'Profil fortsetzen',
createRecovery: 'Wiederherstellungsprofil erstellen',
retryPrimary: 'Hauptprofil erneut prüfen',
cleanupRecoveryTitle: 'Eine Datenbereinigung wurde unterbrochen',
abandonCleanup: 'Verbleibende Daten behalten und fortfahren',
},
es: {
title: 'Iniciando OpenSquilla',
bootAria: 'Inicio de OpenSquilla',
recoveryTitle: 'El perfil principal necesita recuperación',
continueRecovery: 'Continuar perfil de recuperación',
createRecovery: 'Crear perfil de recuperación',
retryPrimary: 'Reintentar perfil principal',
cleanupRecoveryTitle: 'Una limpieza de datos se detuvo a mitad de camino',
abandonCleanup: 'Conservar los datos restantes y continuar',
},
}
const BLOCKING_CASES = {
en: { fixture: 'missing-workspace', stableCode: 'effective_workspace_missing' },
'zh-Hans': { fixture: 'corrupt-config', stableCode: 'config_invalid' },
ja: { fixture: 'future-config', stableCode: 'config_schema_too_new' },
fr: {
fixture: 'future-context',
stableCode: 'desktop_profile_context_schema_too_new',
},
de: { fixture: 'unfinished-transaction', stableCode: 'transaction_incomplete' },
es: { fixture: 'unsafe-database', stableCode: 'state_database_invalid' },
}
async function waitFor(check, label, timeoutMs = 90_000) {
const startedAt = Date.now()
let lastError
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await check()
if (value) return value
} catch (error) {
lastError = error
}
await delay(250)
}
throw new Error(`Timed out waiting for ${label}: ${lastError?.message || lastError || ''}`)
}
async function snapshotTree(root) {
const result = {}
async function visit(path, relative = '') {
const info = await lstat(path)
assert.equal(info.isSymbolicLink(), false, `fixture must not contain symlinks: ${path}`)
if (info.isDirectory()) {
result[`${relative || '.'}/`] = 'directory'
for (const name of (await readdir(path)).sort()) {
await visit(join(path, name), relative ? `${relative}/${name}` : name)
}
return
}
assert.equal(info.isFile(), true, `fixture must contain only files/directories: ${path}`)
result[relative] = (await readFile(path)).toString('base64')
}
await visit(root)
return result
}
function launchEnvironment(isolatedHome) {
const inherited = { ...process.env }
for (const name of Object.keys(inherited)) {
if (name === 'DISPLAY' || name === 'XAUTHORITY') continue
const upperName = name.toUpperCase()
if (
upperName.startsWith('OPENSQUILLA_')
|| ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY'].includes(upperName)
|| /(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH)/i.test(name)
|| /^(?:AWS|AZURE|GOOGLE|ANTHROPIC|OPENAI|OPENROUTER|MINIMAX|DEEPSEEK|GROQ|MISTRAL|COHERE|GEMINI|OLLAMA|XAI|MOONSHOT|DASHSCOPE|SILICONFLOW|ZHIPU|BAIDU|VOLCENGINE|TENCENT|ALIYUN|HF|HUGGINGFACE)_/i.test(name)
) {
delete inherited[name]
}
}
return {
...inherited,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
OPENSQUILLA_DESKTOP_REPO_ROOT: repoRoot,
OPENSQUILLA_DESKTOP_SECRET_STORAGE: 'plain',
OPENSQUILLA_USER_STATE_DIR: join(isolatedHome, 'user-state'),
OPENSQUILLA_TEST_PROFILE_LOCK_ROOT: '1',
OPENSQUILLA_DESKTOP_GATEWAY_PORT: '18897',
OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE: '1',
OPENSQUILLA_OPENROUTER_LIVE_PRICING: '0',
OPENSQUILLA_GATEWAY_WORKSPACE_DIR: '',
OPENSQUILLA_WORKSPACE_DIR: '',
OPENSQUILLA_GATEWAY_STATE_DIR: '',
HTTP_PROXY: 'http://127.0.0.1:1',
HTTPS_PROXY: 'http://127.0.0.1:1',
ALL_PROXY: 'http://127.0.0.1:1',
NO_PROXY: '127.0.0.1,localhost',
http_proxy: 'http://127.0.0.1:1',
https_proxy: 'http://127.0.0.1:1',
all_proxy: 'http://127.0.0.1:1',
no_proxy: '127.0.0.1,localhost',
LANG: 'en_US.UTF-8',
LC_ALL: 'en_US.UTF-8',
}
}
async function createFixture(locale, blockingCase) {
const root = await realpath(await mkdtemp(join(tmpdir(), `opensquilla-recovery-a11y-${locale}-`)))
const userData = join(root, 'user-data')
const isolatedHome = join(root, 'home')
const primaryHome = join(userData, 'opensquilla')
const primaryWorkspace = join(primaryHome, 'workspace')
const primaryState = join(primaryHome, 'state')
const missingWorkspace = join(root, 'missing-external-workspace')
const recoveryHome = join(userData, 'recovery-profiles', RECOVERY_ID, 'opensquilla')
await mkdir(primaryWorkspace, { recursive: true })
await mkdir(primaryState, { recursive: true })
await mkdir(recoveryHome, { recursive: true })
await mkdir(isolatedHome, { recursive: true })
for (const [name, text] of [
['USER.md', 'synthetic accessibility user\n'],
['SOUL.md', 'synthetic accessibility soul\n'],
['IDENTITY.md', 'synthetic accessibility identity\n'],
['MEMORY.md', 'synthetic accessibility memory\n'],
]) {
await writeFile(join(primaryWorkspace, name), text, 'utf8')
}
await writeFile(join(primaryState, 'primary-must-not-change.txt'), 'unchanged\n', 'utf8')
const validConfig = [
`state_dir = ${JSON.stringify(primaryState)}`,
'',
].join('\n')
let config = validConfig
if (blockingCase.fixture === 'missing-workspace') {
config = [
`state_dir = ${JSON.stringify(primaryState)}`,
`workspace_dir = ${JSON.stringify(missingWorkspace)}`,
'',
].join('\n')
} else if (blockingCase.fixture === 'corrupt-config') {
config = 'workspace_dir = "unterminated\n'
} else if (blockingCase.fixture === 'future-config') {
config = 'config_version = 999\n'
}
await writeFile(join(primaryHome, 'config.toml'), config, 'utf8')
if (blockingCase.fixture === 'unsafe-database') {
await writeFile(join(primaryState, 'sessions.db'), 'not a sqlite database', 'utf8')
}
let journalPath = null
let journalBytes = null
if (blockingCase.fixture === 'unfinished-transaction') {
journalPath = join(userData, '.opensquilla.profile-replace.json')
journalBytes = '{"schema_version":1,"phase":"prepared"}\n'
await writeFile(journalPath, journalBytes, 'utf8')
} else if (blockingCase.fixture === 'cleanup-transaction') {
journalPath = join(userData, '.opensquilla.profile-cleanup.json')
journalBytes = 'synthetic interrupted cleanup authority\n'
await writeFile(journalPath, journalBytes, 'utf8')
}
await writeFile(join(userData, 'desktop-locale'), locale, 'utf8')
await writeFile(
join(userData, 'desktop-profile-context.json'),
`${JSON.stringify({
schema_version: blockingCase.fixture === 'future-context' ? 999 : 1,
active_profile_kind: 'primary',
active_recovery_id: null,
attention_acknowledgement: null,
updated_at: '2026-07-11T00:00:00.000Z',
}, null, 2)}\n`,
'utf8',
)
return {
root,
userData,
isolatedHome,
primaryHome,
recoveryHome,
journalPath,
journalBytes,
primaryBefore: await snapshotTree(primaryHome),
}
}
async function launchFixture(fixture) {
return await electron.launch({
args: ['--use-mock-keychain', `--user-data-dir=${fixture.userData}`, packageRoot],
env: launchEnvironment(fixture.isolatedHome),
})
}
async function recoveryPage(app) {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
if (await page.locator('#recoveryPanel.visible').count().catch(() => 0)) return page
}
return null
}, 'localized recovery page')
}
function observeRenderer(page) {
if (observedRendererPages.has(page)) return
observedRendererPages.add(page)
page.on('console', (message) => {
rendererDiagnostics.push({
type: `console:${message.type()}`,
text: message.text().slice(0, 1_000),
})
})
page.on('pageerror', (error) => {
rendererDiagnostics.push({
type: 'pageerror',
text: String(error?.message || error).slice(0, 1_000),
})
})
}
async function onboardingPage(app) {
try {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
observeRenderer(page)
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
if (await page.locator('#setup-form').count().catch(() => 0)) return page
}
return null
}, 'selected recovery profile onboarding')
} catch (error) {
const windows = await Promise.all(app.windows().map(async (page) => ({
url: page.url(),
title: await page.title().catch(() => ''),
body: await page.locator('body').innerText().catch(() => '').then((value) => (
value.slice(0, 1_500)
)),
})))
throw new Error(
`${error.message}; windows=${JSON.stringify(windows)}; `
+ `renderer=${JSON.stringify(rendererDiagnostics.slice(-30))}`,
)
}
}
async function tabTo(page, targetId, maximumTabs = 30) {
for (let index = 0; index <= maximumTabs; index += 1) {
const activeId = await page.evaluate(() => document.activeElement?.id || '')
if (activeId === targetId) return
await page.keyboard.press('Tab')
}
throw new Error(`Keyboard focus did not reach #${targetId}`)
}
function durationSeconds(value) {
if (value.endsWith('ms')) return Number.parseFloat(value) / 1_000
if (value.endsWith('s')) return Number.parseFloat(value)
return Number.NaN
}
async function assertReducedMotion(page) {
await page.emulateMedia({ reducedMotion: 'reduce' })
const result = await page.evaluate(() => {
const bodyWasErrored = document.body.classList.contains('errored')
const blockedStyle = getComputedStyle(
document.querySelector('.status-line'),
'::before',
)
const blockedAnimationName = blockedStyle.animationName
document.body.classList.remove('errored')
const reducedStyle = getComputedStyle(
document.querySelector('.status-line'),
'::before',
)
const snapshot = {
mediaMatches: matchMedia('(prefers-reduced-motion: reduce)').matches,
blockedAnimationName,
animationName: reducedStyle.animationName,
animationDuration: reducedStyle.animationDuration,
animationIterationCount: reducedStyle.animationIterationCount,
scrollBehavior: getComputedStyle(document.documentElement).scrollBehavior,
}
if (bodyWasErrored) document.body.classList.add('errored')
return snapshot
})
assert.equal(result.mediaMatches, true)
assert.equal(result.blockedAnimationName, 'none')
assert.equal(result.animationName, 'progress')
assert(durationSeconds(result.animationDuration) <= 0.00001, result.animationDuration)
assert.equal(result.animationIterationCount, '1')
assert.equal(result.scrollBehavior, 'auto')
}
async function assertLocalizedRecovery(page, locale, expected) {
await waitFor(
async () => await page.locator('html').getAttribute('lang') === locale,
`${locale} locale application`,
)
assert.equal(await page.title(), expected.title)
assert.equal(await page.locator('main.boot').getAttribute('aria-label'), expected.bootAria)
assert.equal(await page.locator('#recoveryTitle').innerText(), expected.recoveryTitle)
assert.equal(await page.locator('#continueRecovery').innerText(), expected.continueRecovery)
assert.equal(await page.locator('#createRecovery').innerText(), expected.createRecovery)
assert.equal(await page.locator('#retryPrimary').innerText(), expected.retryPrimary)
assert.equal(await page.locator('#recoveryPanel').getAttribute('role'), 'region')
assert.equal(await page.locator('#recoveryPanel').getAttribute('aria-labelledby'), 'recoveryTitle')
assert.equal(
await page.getByRole('region', { name: expected.recoveryTitle }).count(),
1,
)
assert.equal(await page.locator('#recoveryStatus').getAttribute('role'), 'status')
assert.equal(await page.locator('#recoveryStatus').getAttribute('aria-live'), 'polite')
assert.equal(await page.evaluate(() => document.activeElement?.id), 'recoveryTitle')
}
const completedLocales = []
const completedBlockingCodes = []
const completedCleanupLocales = []
for (const [locale, expected] of Object.entries(LOCALES)) {
const blockingCase = BLOCKING_CASES[locale]
assert(blockingCase, `missing blocking fixture for ${locale}`)
const fixture = await createFixture(locale, blockingCase)
let app
try {
app = await launchFixture(fixture)
const page = await recoveryPage(app)
assert.deepEqual(
await readdir(fixture.recoveryHome),
[],
'read-only recovery inspection must not seed a third blank workspace',
)
assert.equal(await page.locator('#recoveryCode').innerText(), blockingCase.stableCode)
await assertLocalizedRecovery(page, locale, expected)
const originalRecoveryState = await page.evaluate(() => (
window.opensquillaDesktop.getRecoveryState()
))
await page.evaluate((state) => {
window.renderRecoveryState({
...state,
blocked: true,
inspection: {
...state.inspection,
outcome: 'recovery_required',
stable_code: 'cleanup_transaction_incomplete',
allowed_actions: [
'abandon-cleanup',
'continue-recovery-profile',
'create-recovery-profile',
'retry-primary-profile',
'show-backups',
'copy-diagnostics',
],
},
}, false)
}, originalRecoveryState)
assert.equal(await page.locator('#recoveryTitle').innerText(), expected.cleanupRecoveryTitle)
assert.equal(await page.locator('#abandonCleanup').innerText(), expected.abandonCleanup)
assert.equal(await page.locator('#cleanupAbandonGroup').getAttribute('hidden'), null)
completedCleanupLocales.push(locale)
await page.evaluate((state) => window.renderRecoveryState(state, true), originalRecoveryState)
if (locale === 'en') await assertReducedMotion(page)
const existingProfiles = await page.locator('#recoveryProfiles option').evaluateAll((options) => (
options.map((option) => ({ value: option.value, label: option.textContent || '' }))
))
assert(existingProfiles.some((option) => (
option.value === RECOVERY_ID && option.label.includes(fixture.recoveryHome)
)))
const gatewayBeforeChoice = await page.evaluate(() => (
window.opensquillaDesktop.getGatewayStatus()
))
assert.equal(gatewayBeforeChoice.status, 'stopped')
assert.equal(gatewayBeforeChoice.owned, false)
// The entire fallback selection is keyboard-only. Starting from the
// programmatically focused recovery heading, Tab reaches the existing
// profile selector, then Enter activates Continue without a pointer click.
await tabTo(page, 'recoveryProfiles')
assert.equal(await page.locator('#recoveryProfiles').inputValue(), RECOVERY_ID)
await page.keyboard.press('Home')
assert.equal(await page.locator('#recoveryProfiles').inputValue(), RECOVERY_ID)
await page.keyboard.press('Tab')
assert.equal(await page.evaluate(() => document.activeElement?.id), 'continueRecovery')
await page.keyboard.press('Enter')
const onboarding = await onboardingPage(app)
const gatewayAfterChoice = await onboarding.evaluate(() => (
window.opensquillaDesktop.getGatewayStatus()
))
assert.equal(gatewayAfterChoice.status, 'stopped')
assert.equal(gatewayAfterChoice.owned, false)
const context = JSON.parse(
await readFile(join(fixture.userData, 'desktop-profile-context.json'), 'utf8'),
)
assert.equal(context.active_profile_kind, 'recovery')
assert.equal(context.active_recovery_id, RECOVERY_ID)
assert.deepEqual(await snapshotTree(fixture.primaryHome), fixture.primaryBefore)
if (fixture.journalPath) {
assert.equal(await readFile(fixture.journalPath, 'utf8'), fixture.journalBytes)
}
completedLocales.push(locale)
completedBlockingCodes.push(blockingCase.stableCode)
} finally {
await app?.close().catch(() => {})
await rm(fixture.root, { recursive: true, force: true }).catch(() => {})
}
}
assert.deepEqual(completedLocales, Object.keys(LOCALES))
assert.deepEqual(completedCleanupLocales, Object.keys(LOCALES))
assert.deepEqual(
completedBlockingCodes,
Object.values(BLOCKING_CASES).map((item) => item.stableCode),
)
// A real compiled Electron launch also covers the cleanup-specific recovery
// state. The destructive action itself is not activated here (the native
// confirmation is intentionally outside renderer automation); keyboard focus,
// localized explanation, ARIA region, and the untouched primary bytes are.
const cleanupFixture = await createFixture('en', {
fixture: 'cleanup-transaction',
stableCode: 'cleanup_transaction_incomplete',
})
let cleanupApp
try {
cleanupApp = await launchFixture(cleanupFixture)
const cleanupPage = await recoveryPage(cleanupApp)
assert.equal(await cleanupPage.locator('#recoveryCode').innerText(), 'cleanup_transaction_incomplete')
assert.equal(await cleanupPage.locator('#recoveryTitle').innerText(), LOCALES.en.cleanupRecoveryTitle)
assert.equal(await cleanupPage.locator('#cleanupAbandonGroup').getAttribute('hidden'), null)
assert.equal(await cleanupPage.locator('#abandonCleanup').innerText(), LOCALES.en.abandonCleanup)
assert.equal(await cleanupPage.locator('#recoveryPanel').getAttribute('role'), 'region')
assert.equal(await cleanupPage.locator('#recoveryStatus').getAttribute('aria-live'), 'polite')
await tabTo(cleanupPage, 'abandonCleanup')
assert.equal(await cleanupPage.evaluate(() => document.activeElement?.id), 'abandonCleanup')
assert.deepEqual(await snapshotTree(cleanupFixture.primaryHome), cleanupFixture.primaryBefore)
assert.equal(
await readFile(cleanupFixture.journalPath, 'utf8'),
cleanupFixture.journalBytes,
)
} finally {
await cleanupApp?.close().catch(() => {})
await rm(cleanupFixture.root, { recursive: true, force: true }).catch(() => {})
}
console.log(JSON.stringify({
ok: true,
locales: completedLocales,
blockingCodes: completedBlockingCodes,
keyboardContinueVerified: true,
primaryBytesUnchanged: true,
reducedMotionVerified: true,
cleanupAbandonKeyboardVerified: true,
}))
@@ -0,0 +1,667 @@
import { strict as assert } from 'node:assert'
import { spawnSync } from 'node:child_process'
import { createServer } from 'node:http'
import {
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
realpath,
rm,
writeFile,
} from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { fileURLToPath } from 'node:url'
import { _electron as electron } from 'playwright'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '../..')
const screenshotPath = String(process.env.OPENSQUILLA_DESKTOP_RECOVERY_SCREENSHOT || '').trim()
const PRIMARY_SENTINEL = 'synthetic-primary-credential-must-not-be-copied'
const RECOVERY_SYNTHETIC_KEY = 'synthetic-loopback-only-recovery-key'
const FIRST_PROMPT = 'RECOVERY_E2E_FIRST_PROMPT'
const SECOND_PROMPT = 'RECOVERY_E2E_SECOND_PROMPT'
const REPLY = 'RECOVERY_E2E_REPLY'
const observedRendererPages = new WeakSet()
const rendererDiagnostics = []
async function waitFor(check, label, timeoutMs = 90_000) {
const startedAt = Date.now()
let lastError
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await check()
if (value) return value
} catch (error) {
lastError = error
}
await delay(250)
}
throw new Error(`Timed out waiting for ${label}: ${lastError?.message || lastError || ''}`)
}
function runPython(source, args) {
const result = spawnSync('uv', ['run', 'python', '-c', source, ...args], {
cwd: repoRoot,
encoding: 'utf8',
})
if (result.status !== 0) {
throw new Error(`Python fixture command failed: ${result.stderr || result.stdout}`)
}
return result.stdout.trim()
}
async function snapshotTree(root) {
const result = {}
async function visit(path, relative = '') {
const info = await lstat(path)
assert.equal(info.isSymbolicLink(), false, `fixture must not contain symlinks: ${path}`)
if (info.isDirectory()) {
result[`${relative || '.'}/`] = 'directory'
for (const name of (await readdir(path)).sort()) {
await visit(join(path, name), relative ? `${relative}/${name}` : name)
}
return
}
assert.equal(info.isFile(), true, `fixture must contain only files/directories: ${path}`)
result[relative] = (await readFile(path)).toString('base64')
}
await visit(root)
return result
}
async function snapshotPrimary(profile, credentialPath) {
return {
profile: await snapshotTree(profile),
credential: (await readFile(credentialPath)).toString('base64'),
}
}
async function recoveryPage(app) {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
if (await page.locator('#recoveryPanel.visible').count().catch(() => 0)) return page
}
return null
}, 'unsafe-primary recovery page')
}
async function onboardingPage(app) {
return await waitFor(async () => {
for (const page of app.windows()) {
if (page.isClosed()) continue
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
if (await page.locator('#setup-form').count().catch(() => 0)) return page
}
return null
}, 'recovery-profile onboarding window')
}
async function controlPage(app) {
const observe = (page) => {
if (observedRendererPages.has(page)) return
observedRendererPages.add(page)
page.on('console', (message) => {
rendererDiagnostics.push({ type: `console:${message.type()}`, text: message.text().slice(0, 1_000) })
})
page.on('pageerror', (error) => {
rendererDiagnostics.push({ type: 'pageerror', text: String(error?.message || error).slice(0, 1_000) })
})
}
try {
const page = await waitFor(async () => {
for (const candidate of app.windows()) {
if (candidate.isClosed()) continue
observe(candidate)
await candidate.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
let pathname = ''
try { pathname = new URL(candidate.url()).pathname } catch { pathname = '' }
if (pathname !== '/control/chat' && pathname !== '/control/chat/new') continue
if (await candidate.locator('.chat-textarea').count().catch(() => 0)) return candidate
}
return null
}, 'recovery-profile Control UI', 120_000)
await waitFor(() => {
try { return new URL(page.url()).pathname === '/control/chat/new' } catch { return false }
}, 'new-chat draft route', 30_000)
return page
} catch (error) {
const windows = await Promise.all(app.windows().map(async (page) => ({
url: page.url(),
title: await page.title().catch(() => ''),
body: await page.locator('body').innerText().catch(() => '').then((value) => value.slice(0, 1_500)),
})))
throw new Error(
`${error.message}; windows=${JSON.stringify(windows)}; renderer=${JSON.stringify(rendererDiagnostics.slice(-30))}`,
)
}
}
async function sendChat(page, prompt) {
const textarea = page.locator('.chat-textarea')
await textarea.waitFor({ state: 'visible', timeout: 30_000 })
try {
await waitFor(async () => {
// The Control UI can finish one last reactive render after its textarea
// first becomes visible. Refill before sending if that render replaced
// the input; never press Enter until the exact synthetic prompt remains.
if (await textarea.inputValue().catch(() => '') !== prompt) {
await textarea.fill(prompt)
}
return await page.locator('.chat-send-btn.is-ready').count().catch(() => 0)
}, 'ready recovery chat composer', 10_000)
} catch (error) {
const state = await page.evaluate(() => ({
href: window.location.href,
sessionKey: document.querySelector('.chat-label')?.getAttribute('title') || '',
textareaValue: document.querySelector('.chat-textarea')?.value || '',
sendButtonClass: document.querySelector('.chat-send-btn')?.className || '',
bodyText: document.body.innerText.slice(0, 1_000),
})).catch(() => ({ unavailable: true }))
throw new Error(`${error.message}; composer=${JSON.stringify(state)}`)
}
await textarea.press('Enter')
await page.locator('.msg-ai').filter({ hasText: REPLY }).last().waitFor({
state: 'visible',
timeout: 60_000,
})
await waitFor(async () => (
await page.locator('.chat-thread').getAttribute('aria-busy') === 'false'
), 'completed recovery chat turn', 60_000)
}
function launchEnvironment(isolatedHome, providerPort, sourceEnvironment = process.env) {
const inherited = { ...sourceEnvironment }
for (const name of Object.keys(inherited)) {
if (name === 'DISPLAY' || name === 'XAUTHORITY') continue
const upperName = name.toUpperCase()
if (
name.startsWith('OPENSQUILLA_')
|| ['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY'].includes(upperName)
|| /(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH)/i.test(name)
|| /^(?:AWS|AZURE|GOOGLE|ANTHROPIC|OPENAI|OPENROUTER|MINIMAX|DEEPSEEK|GROQ|MISTRAL|COHERE|GEMINI|OLLAMA|XAI|MOONSHOT|DASHSCOPE|SILICONFLOW|ZHIPU|BAIDU|VOLCENGINE|TENCENT|ALIYUN|HF|HUGGINGFACE)_/i.test(name)
) {
delete inherited[name]
}
}
return {
...inherited,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
OPENSQUILLA_DESKTOP_REPO_ROOT: repoRoot,
OPENSQUILLA_DESKTOP_SECRET_STORAGE: 'plain',
OPENSQUILLA_USER_STATE_DIR: join(isolatedHome, 'user-state'),
OPENSQUILLA_TEST_PROFILE_LOCK_ROOT: '1',
OPENSQUILLA_DESKTOP_GATEWAY_PORT: '18898',
OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE: '1',
OPENSQUILLA_OPENROUTER_LIVE_PRICING: '0',
OPENSQUILLA_GATEWAY_WORKSPACE_DIR: '',
OPENSQUILLA_WORKSPACE_DIR: '',
OPENSQUILLA_GATEWAY_STATE_DIR: '',
OPENSQUILLA_E2E_PROVIDER_PORT: String(providerPort),
PYTHONFAULTHANDLER: '1',
HTTP_PROXY: 'http://127.0.0.1:1',
HTTPS_PROXY: 'http://127.0.0.1:1',
ALL_PROXY: 'http://127.0.0.1:1',
NO_PROXY: '127.0.0.1,localhost',
http_proxy: 'http://127.0.0.1:1',
https_proxy: 'http://127.0.0.1:1',
all_proxy: 'http://127.0.0.1:1',
no_proxy: '127.0.0.1,localhost',
LANG: 'en_US.UTF-8',
LC_ALL: 'en_US.UTF-8',
}
}
async function launchDesktop(userData, isolatedHome, providerPort) {
return await electron.launch({
args: ['--use-mock-keychain', `--user-data-dir=${userData}`, packageRoot],
env: launchEnvironment(isolatedHome, providerPort),
})
}
async function startFakeProvider() {
const requests = []
const server = createServer(async (request, response) => {
const chunks = []
for await (const chunk of request) chunks.push(chunk)
const raw = Buffer.concat(chunks).toString('utf8')
let payload = {}
try { payload = raw ? JSON.parse(raw) : {} } catch { payload = {} }
requests.push({ method: request.method, url: request.url, payload })
if (request.method === 'GET' && request.url?.endsWith('/models')) {
response.writeHead(200, { 'content-type': 'application/json' })
response.end(JSON.stringify({ object: 'list', data: [{ id: 'synthetic-recovery-model' }] }))
return
}
if (request.method === 'GET' && request.url?.endsWith('/api/tags')) {
response.writeHead(200, { 'content-type': 'application/json' })
response.end(JSON.stringify({ models: [{ name: 'synthetic-recovery-model' }] }))
return
}
if (request.method !== 'POST' || !request.url?.endsWith('/chat/completions')) {
response.writeHead(404, { 'content-type': 'application/json' })
response.end(JSON.stringify({ error: { message: 'synthetic endpoint not found' } }))
return
}
if (payload.stream === false) {
response.writeHead(200, { 'content-type': 'application/json' })
response.end(JSON.stringify({
id: 'chatcmpl-recovery-title',
object: 'chat.completion',
model: 'synthetic-recovery-model',
choices: [{ index: 0, message: { role: 'assistant', content: 'Recovery chat' }, finish_reason: 'stop' }],
usage: { prompt_tokens: 8, completion_tokens: 2, total_tokens: 10 },
}))
return
}
response.writeHead(200, {
'content-type': 'text/event-stream',
'cache-control': 'no-cache',
connection: 'close',
})
response.write(`data: ${JSON.stringify({
id: 'chatcmpl-recovery-e2e',
object: 'chat.completion.chunk',
model: 'synthetic-recovery-model',
choices: [{ index: 0, delta: { role: 'assistant', content: REPLY }, finish_reason: null }],
})}\n\n`)
response.write(`data: ${JSON.stringify({
id: 'chatcmpl-recovery-e2e',
object: 'chat.completion.chunk',
model: 'synthetic-recovery-model',
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
usage: { prompt_tokens: 12, completion_tokens: 3, total_tokens: 15 },
})}\n\n`)
response.end('data: [DONE]\n\n')
})
await new Promise((resolveListen, rejectListen) => {
server.once('error', rejectListen)
server.listen(0, '127.0.0.1', resolveListen)
})
const address = server.address()
assert(address && typeof address === 'object')
return {
port: address.port,
requests,
close: () => new Promise((resolveClose, rejectClose) => {
server.close((error) => error ? rejectClose(error) : resolveClose())
}),
}
}
const root = await realpath(await mkdtemp(join(tmpdir(), 'opensquilla-electron-recovery-test-')))
const userData = join(root, 'user-data')
const isolatedHome = join(root, 'home')
const primaryHome = join(userData, 'opensquilla')
const primaryWorkspace = join(primaryHome, 'workspace')
const primaryState = join(primaryHome, 'state')
const primaryDatabase = join(primaryState, 'sessions.db')
const primaryCredential = join(userData, 'desktop-credential.json')
const missingWorkspace = join(root, 'missing-external-workspace')
await mkdir(primaryWorkspace, { recursive: true })
await mkdir(primaryState, { recursive: true })
await mkdir(isolatedHome, { recursive: true })
for (const [name, text] of [
['USER.md', 'synthetic primary user\n'],
['SOUL.md', 'synthetic primary soul\n'],
['IDENTITY.md', 'synthetic primary identity\n'],
['MEMORY.md', 'synthetic primary memory\n'],
]) {
await writeFile(join(primaryWorkspace, name), text, 'utf8')
}
await writeFile(
join(primaryHome, 'config.toml'),
[
`state_dir = ${JSON.stringify(primaryState)}`,
`workspace_dir = ${JSON.stringify(missingWorkspace)}`,
'',
'[llm]',
'provider = "ollama"',
'model = "primary-must-not-run"',
'base_url = "http://127.0.0.1:9/v1"',
'',
].join('\n'),
'utf8',
)
await writeFile(
primaryCredential,
JSON.stringify({
provider: 'ollama',
model: 'primary-must-not-run',
baseUrl: 'http://127.0.0.1:9/v1',
encryptedApiKey: PRIMARY_SENTINEL,
modelRoutingMode: 'direct',
routerMode: 'disabled',
searchProvider: 'duckduckgo',
encryption: 'plain',
createdAt: '2026-07-11T00:00:00.000Z',
updatedAt: '2026-07-11T00:00:00.000Z',
}, null, 2),
'utf8',
)
runPython(
'import sqlite3,sys; c=sqlite3.connect(sys.argv[1]); '
+ 'c.execute("CREATE TABLE synthetic_primary_sessions (id TEXT PRIMARY KEY, body TEXT)"); '
+ 'c.execute("INSERT INTO synthetic_primary_sessions VALUES (?, ?)", '
+ '("primary-session", "primary transcript must remain unchanged")); c.commit(); c.close()',
[primaryDatabase],
)
const primaryBefore = await snapshotPrimary(primaryHome, primaryCredential)
const fakeProvider = await startFakeProvider()
const scrubbedEnvironmentProbe = launchEnvironment(isolatedHome, fakeProvider.port, {
...process.env,
OPENAI_API_KEY: 'synthetic-real-provider-key-must-not-leak',
AWS_PROFILE: 'synthetic-real-provider-profile-must-not-leak',
OPENSQUILLA_STATE_DIR: '/synthetic/external/state/must-not-leak',
})
assert.equal(scrubbedEnvironmentProbe.OPENAI_API_KEY, undefined)
assert.equal(scrubbedEnvironmentProbe.AWS_PROFILE, undefined)
assert.equal(scrubbedEnvironmentProbe.OPENSQUILLA_STATE_DIR, undefined)
assert.equal(scrubbedEnvironmentProbe.HTTP_PROXY, 'http://127.0.0.1:1')
assert.equal(scrubbedEnvironmentProbe.NO_PROXY, '127.0.0.1,localhost')
let app
try {
app = await launchDesktop(userData, isolatedHome, fakeProvider.port)
const recovery = await recoveryPage(app)
assert.equal(await recovery.locator('#recoveryCode').innerText(), 'effective_workspace_missing')
assert.equal(await recovery.locator('#copyCredential').isChecked(), false)
if (screenshotPath) {
await mkdir(dirname(screenshotPath), { recursive: true })
await recovery.screenshot({ path: screenshotPath })
}
await recovery.locator('#createRecovery').click()
const setup = await onboardingPage(app)
await setup.locator('[data-screen="0"].active .next-button').click()
await setup.locator('[data-screen="1"].active').waitFor({ state: 'visible', timeout: 10_000 })
await setup.locator('#providerMoreToggle').click()
await setup.locator('#providerGrid [data-provider="minimax_openai"]').click()
await setup.locator('#baseUrl').fill(`http://127.0.0.1:${fakeProvider.port}/v1`)
await setup.locator('#model').fill('synthetic-recovery-model')
await setup.locator('#apiKey').fill(RECOVERY_SYNTHETIC_KEY)
await setup.locator('[data-screen="1"].active .next-button').click()
await setup.locator('[data-screen="4"].active').waitFor({ state: 'visible', timeout: 10_000 })
await setup.locator('#finish').click()
const firstControl = await controlPage(app)
const recoveryIds = (await readdir(join(userData, 'recovery-profiles'))).sort()
assert.equal(recoveryIds.length, 1)
const recoveryId = recoveryIds[0]
assert.match(recoveryId, /^[0-9a-f-]{36}$/i)
const recoveryRoot = join(userData, 'recovery-profiles', recoveryId)
const recoveryHome = join(recoveryRoot, 'opensquilla')
const recoveryWorkspace = join(recoveryHome, 'workspace')
const recoveryState = join(recoveryHome, 'state')
const recoveryCredential = await readFile(join(recoveryRoot, 'desktop-credential.json'), 'utf8')
assert.doesNotMatch(recoveryCredential, new RegExp(PRIMARY_SENTINEL))
assert.equal(JSON.parse(recoveryCredential).provider, 'minimax_openai')
for (const name of ['USER.md', 'SOUL.md', 'IDENTITY.md', 'MEMORY.md']) {
assert.equal((await lstat(join(recoveryWorkspace, name))).isFile(), true)
}
assert.deepEqual(await snapshotPrimary(primaryHome, primaryCredential), primaryBefore)
await sendChat(firstControl, FIRST_PROMPT)
await waitFor(
() => fakeProvider.requests.some((item) => JSON.stringify(item.payload).includes(FIRST_PROMPT)),
'first prompt at local fake provider',
)
assert.deepEqual(await snapshotPrimary(primaryHome, primaryCredential), primaryBefore)
await app.close()
app = null
const recoveryDatabase = join(recoveryState, 'sessions.db')
const firstTranscript = JSON.parse(runPython(
'import json,sqlite3,sys; c=sqlite3.connect(f"file:{sys.argv[1]}?mode=ro", uri=True); '
+ 'rows=c.execute("SELECT role,content FROM transcript_entries ORDER BY id").fetchall(); '
+ 'c.close(); print(json.dumps(rows))',
[recoveryDatabase],
))
assert(firstTranscript.some(([role, content]) => role === 'user' && String(content).includes(FIRST_PROMPT)))
assert(firstTranscript.some(([role, content]) => role === 'assistant' && String(content).includes(REPLY)))
assert.deepEqual(await snapshotPrimary(primaryHome, primaryCredential), primaryBefore)
const persistedBeforeRestart = JSON.parse(
await readFile(join(userData, 'desktop-profile-context.json'), 'utf8'),
)
assert.equal(persistedBeforeRestart.active_profile_kind, 'recovery')
assert.equal(persistedBeforeRestart.active_recovery_id, recoveryId)
app = await launchDesktop(userData, isolatedHome, fakeProvider.port)
const restartedRecovery = await recoveryPage(app)
assert.equal(
await restartedRecovery.locator('#recoveryCode').innerText(),
'desktop_recovery_profile_confirmation_required',
)
assert.equal(
await restartedRecovery.locator('#recoveryTitle').innerText(),
'Confirm recovery profile',
)
assert.equal(
await restartedRecovery.locator('#recoveryIntro').innerText(),
'OpenSquilla is waiting for you to confirm the selected isolated recovery profile before it starts. This does not mean your primary profile is unsafe.',
)
const persistedWhileBlocked = JSON.parse(
await readFile(join(userData, 'desktop-profile-context.json'), 'utf8'),
)
assert.equal(persistedWhileBlocked.active_profile_kind, 'recovery')
assert.equal(persistedWhileBlocked.active_recovery_id, recoveryId)
const existingOptions = await restartedRecovery.locator('#recoveryProfiles option').evaluateAll((items) => (
items.map((item) => ({ value: item.value, label: item.textContent || '' }))
))
assert(existingOptions.some((item) => item.value === recoveryId && item.label.includes(recoveryHome)))
await restartedRecovery.locator('#recoveryProfiles').selectOption(recoveryId)
await restartedRecovery.locator('#continueRecovery').click()
const secondControl = await controlPage(app)
const activeState = await secondControl.evaluate(() => window.opensquillaDesktop.getRecoveryState())
assert.equal(activeState.activeProfile.kind, 'recovery')
assert.equal(activeState.activeProfile.recoveryId, recoveryId)
assert.equal(activeState.activeProfile.home, recoveryHome)
const persistedConversation = secondControl.locator('.sidebar-history-item', {
hasText: 'Recovery chat',
})
await persistedConversation.waitFor({ state: 'visible', timeout: 30_000 })
await persistedConversation.click()
await waitFor(() => secondControl.url().includes('?session='), 'persisted recovery chat route')
await secondControl.locator('.msg-user').filter({ hasText: FIRST_PROMPT }).last().waitFor({
state: 'visible',
timeout: 30_000,
})
await secondControl.locator('.msg-ai').filter({ hasText: REPLY }).last().waitFor({
state: 'visible',
timeout: 30_000,
})
await sendChat(secondControl, SECOND_PROMPT)
await waitFor(
() => fakeProvider.requests.some((item) => JSON.stringify(item.payload).includes(SECOND_PROMPT)),
'second prompt at local fake provider',
)
// Reset is a credential/onboarding action, not a profile wipe. Trigger it
// from the live Control UI and prove that config, identity Markdown, and the
// recovery chat database remain in place after the gateway has drained.
const recoveryConfigBeforeReset = await readFile(join(recoveryHome, 'config.toml'))
await secondControl.evaluate(() => {
void window.opensquillaDesktop.resetDesktopSettings()
return true
})
await onboardingPage(app)
assert.deepEqual(
await readFile(join(recoveryHome, 'config.toml')),
recoveryConfigBeforeReset,
'Reset setup must preserve config.toml byte-for-byte',
)
await assert.rejects(
lstat(join(recoveryRoot, 'desktop-credential.json')),
(error) => error && error.code === 'ENOENT',
)
for (const name of ['USER.md', 'SOUL.md', 'IDENTITY.md', 'MEMORY.md']) {
assert.equal((await lstat(join(recoveryWorkspace, name))).isFile(), true)
}
await app.close()
app = null
const finalTranscript = JSON.parse(runPython(
'import json,sqlite3,sys; c=sqlite3.connect(f"file:{sys.argv[1]}?mode=ro", uri=True); '
+ 'rows=c.execute("SELECT role,content FROM transcript_entries ORDER BY id").fetchall(); '
+ 'c.close(); print(json.dumps(rows))',
[recoveryDatabase],
))
for (const prompt of [FIRST_PROMPT, SECOND_PROMPT]) {
assert(finalTranscript.some(([role, content]) => role === 'user' && String(content).includes(prompt)))
}
assert(finalTranscript.filter(([role, content]) => role === 'assistant' && String(content).includes(REPLY)).length >= 2)
assert.deepEqual(await snapshotPrimary(primaryHome, primaryCredential), primaryBefore)
console.log(JSON.stringify({
ok: true,
recoveryId,
chatsCompleted: 2,
explicitContinueVerified: true,
resetPreservedProfileData: true,
primaryBytesUnchanged: true,
localProviderRequests: fakeProvider.requests.length,
}, null, 2))
} catch (error) {
const requestSummary = fakeProvider.requests.map((item) => ({
method: item.method,
url: item.url,
stream: item.payload?.stream,
}))
const recoveryIds = await readdir(join(userData, 'recovery-profiles')).catch(() => [])
const desktopLog = await readFile(join(userData, 'logs', 'desktop.log'), 'utf8').catch(() => '')
const gatewayPid = desktopLog
.trim()
.split('\n')
.reverse()
.map((line) => {
try { return JSON.parse(line) } catch { return null }
})
.find((entry) => entry?.event === 'gateway_spawned' && Number.isSafeInteger(entry.pid))
?.pid
let gatewayProcessTree = []
const gatewaySamples = []
let faulthandlerSignal = { attempted: false }
let sessionsDbLsof = { attempted: false }
if (process.platform === 'darwin' && gatewayPid && process.env.CI_REPORT_DIR) {
const processResult = spawnSync('/bin/ps', ['-axo', 'pid=,ppid=,command='], {
encoding: 'utf8',
timeout: 10_000,
})
const processes = String(processResult.stdout || '')
.split('\n')
.map((line) => line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/))
.filter(Boolean)
.map((match) => ({ pid: Number(match[1]), ppid: Number(match[2]), command: match[3] }))
const descendantPids = new Set([gatewayPid])
let changed = true
while (changed) {
changed = false
for (const candidate of processes) {
if (!descendantPids.has(candidate.ppid) || descendantPids.has(candidate.pid)) continue
descendantPids.add(candidate.pid)
changed = true
}
}
gatewayProcessTree = processes.filter((candidate) => descendantPids.has(candidate.pid))
for (const candidate of gatewayProcessTree) {
const samplePath = join(
process.env.CI_REPORT_DIR,
`gateway-process-${candidate.pid}.sample.txt`,
)
const result = spawnSync(
'/usr/bin/sample',
[String(candidate.pid), '3', '-file', samplePath],
{ encoding: 'utf8', timeout: 10_000 },
)
gatewaySamples.push({
pid: candidate.pid,
status: result.status,
signal: result.signal,
error: result.error?.message || '',
stderr: String(result.stderr || '').slice(-1_000),
})
}
if (recoveryIds.length === 1) {
const sessionsDb = join(
userData,
'recovery-profiles',
recoveryIds[0],
'opensquilla',
'state',
'sessions.db',
)
const result = spawnSync('/usr/sbin/lsof', ['-n', '-P', '--', sessionsDb], {
encoding: 'utf8',
timeout: 10_000,
})
const output = `${result.stdout || ''}${result.stderr || ''}`
await writeFile(join(process.env.CI_REPORT_DIR, 'sessions-db-lsof.txt'), output, 'utf8')
sessionsDbLsof = { attempted: true, status: result.status, bytes: output.length }
}
const pythonChild = gatewayProcessTree.find((candidate) => (
candidate.pid !== gatewayPid && /(?:^|[/\\])python(?:3(?:\.\d+)?)?(?:\s|$)/i.test(candidate.command)
))
if (pythonChild) {
try {
process.kill(pythonChild.pid, 'SIGABRT')
faulthandlerSignal = { attempted: true, pid: pythonChild.pid, sent: true }
await delay(1_000)
} catch (signalError) {
faulthandlerSignal = {
attempted: true,
pid: pythonChild.pid,
sent: false,
error: signalError?.message || String(signalError),
}
}
}
}
const gatewayLog = recoveryIds.length === 1
? await readFile(
join(userData, 'recovery-profiles', recoveryIds[0], 'logs', 'gateway.log'),
'utf8',
).catch(() => '')
: ''
const debugLog = recoveryIds.length === 1
? await readFile(
join(userData, 'recovery-profiles', recoveryIds[0], 'opensquilla', 'logs', 'debug.log'),
'utf8',
).catch(() => '')
: ''
const recoveryStateEntries = recoveryIds.length === 1
? await readdir(
join(userData, 'recovery-profiles', recoveryIds[0], 'opensquilla', 'state'),
{ withFileTypes: true },
).then((entries) => entries.map((entry) => ({
name: entry.name,
kind: entry.isFile() ? 'file' : entry.isDirectory() ? 'directory' : 'other',
}))).catch(() => [])
: []
console.error(JSON.stringify({
requestSummary,
desktopLogTail: desktopLog.slice(-4000),
gatewayProcessTree,
gatewaySamples,
faulthandlerSignal,
sessionsDbLsof,
gatewayLogTail: gatewayLog.slice(-4000),
debugLogTail: debugLog.slice(-8000),
recoveryStateEntries,
}, null, 2))
throw error
} finally {
await app?.close().catch(() => {})
await fakeProvider.close().catch(() => {})
await rm(root, { recursive: true, force: true }).catch(() => {})
}
@@ -0,0 +1,76 @@
import assert from 'node:assert/strict'
import {
macCodeSignatureIsAdHoc,
secretStorageBackendForPolicy,
shouldUseChromiumMockKeychainForPolicy,
} from '../dist/secret-storage-policy.js'
assert.equal(macCodeSignatureIsAdHoc('Signature=adhoc'), true)
assert.equal(macCodeSignatureIsAdHoc('CodeDirectory flags=0x10002(adhoc,runtime)'), true)
assert.equal(macCodeSignatureIsAdHoc('Authority=Developer ID Application: OpenSquilla'), false)
assert.equal(secretStorageBackendForPolicy({
envMode: undefined,
platform: 'darwin',
appPackaged: true,
codesignDiagnostic: 'Signature=adhoc',
}), 'plain')
assert.equal(secretStorageBackendForPolicy({
envMode: undefined,
platform: 'darwin',
appPackaged: true,
codesignDiagnostic: 'Authority=Developer ID Application: OpenSquilla',
}), 'safeStorage')
assert.equal(secretStorageBackendForPolicy({
envMode: 'safeStorage',
platform: 'darwin',
appPackaged: true,
codesignDiagnostic: 'Signature=adhoc',
}), 'safeStorage')
assert.equal(secretStorageBackendForPolicy({
envMode: 'plain',
platform: 'darwin',
appPackaged: true,
codesignDiagnostic: 'Authority=Developer ID Application: OpenSquilla',
}), 'plain')
assert.equal(secretStorageBackendForPolicy({
envMode: undefined,
platform: 'win32',
appPackaged: true,
codesignDiagnostic: '',
}), 'safeStorage')
assert.equal(shouldUseChromiumMockKeychainForPolicy({
envMode: undefined,
platform: 'darwin',
appPackaged: true,
codesignDiagnostic: 'Signature=adhoc',
}), true)
assert.equal(shouldUseChromiumMockKeychainForPolicy({
envMode: 'safeStorage',
platform: 'darwin',
appPackaged: true,
codesignDiagnostic: 'Signature=adhoc',
}), false)
assert.equal(shouldUseChromiumMockKeychainForPolicy({
envMode: undefined,
platform: 'darwin',
appPackaged: true,
codesignDiagnostic: 'Authority=Developer ID Application: OpenSquilla',
}), false)
assert.equal(shouldUseChromiumMockKeychainForPolicy({
envMode: undefined,
platform: 'win32',
appPackaged: true,
codesignDiagnostic: '',
}), false)
console.log('Secret storage policy tests passed.')
@@ -0,0 +1,103 @@
import { strict as assert } from 'node:assert'
import { mkdir, mkdtemp, readFile, readdir, realpath, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { setTimeout as delay } from 'node:timers/promises'
import { fileURLToPath } from 'node:url'
import { _electron as electron } from 'playwright'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '../..')
const recoveryId = '11234567-89ab-4cde-8fab-0123456789ab'
async function waitFor(check, label, timeoutMs = 60_000) {
const startedAt = Date.now()
let lastError
while (Date.now() - startedAt < timeoutMs) {
try {
const value = await check()
if (value) return value
} catch (error) {
lastError = error
}
await delay(250)
}
throw new Error(`Timed out waiting for ${label}: ${lastError?.message || lastError || ''}`)
}
const root = await realpath(await mkdtemp(join(tmpdir(), 'opensquilla-unsafe-profile-test-')))
const userData = join(root, 'user-data')
const isolatedHome = join(root, 'home')
const outside = join(root, 'outside')
const recoveryRoot = join(userData, 'recovery-profiles')
const selectedRoot = join(recoveryRoot, recoveryId)
await mkdir(recoveryRoot, { recursive: true })
await mkdir(isolatedHome, { recursive: true })
await mkdir(outside, { recursive: true })
const unsafeUpdateState = join(outside, 'opensquilla', 'state', 'desktop-update.json')
const unsafeUpdateBytes = JSON.stringify({
snoozedVersion: '0.0.1',
snoozedUntil: '2099-01-01T00:00:00.000Z',
}, null, 2)
await mkdir(dirname(unsafeUpdateState), { recursive: true })
await writeFile(unsafeUpdateState, unsafeUpdateBytes, 'utf8')
await symlink(outside, selectedRoot, process.platform === 'win32' ? 'junction' : 'dir')
await writeFile(
join(userData, 'desktop-profile-context.json'),
JSON.stringify({
schema_version: 1,
active_profile_kind: 'recovery',
active_recovery_id: recoveryId,
attention_acknowledgement: null,
updated_at: '2026-07-11T00:00:00.000Z',
}, null, 2),
'utf8',
)
let app
try {
app = await electron.launch({
args: ['--use-mock-keychain', `--user-data-dir=${userData}`, packageRoot],
env: {
...process.env,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
OPENSQUILLA_DESKTOP_REPO_ROOT: repoRoot,
OPENSQUILLA_DESKTOP_SECRET_STORAGE: 'plain',
OPENSQUILLA_USER_STATE_DIR: join(isolatedHome, 'user-state'),
OPENSQUILLA_TEST_PROFILE_LOCK_ROOT: '1',
OPENSQUILLA_DESKTOP_GATEWAY_PORT: '18896',
OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE: '1',
OPENSQUILLA_DESKTOP_MOCK_UPDATE_VERSION: '9.9.9',
LANG: 'en_US.UTF-8',
LC_ALL: 'en_US.UTF-8',
},
})
const page = await waitFor(async () => {
for (const candidate of app.windows()) {
if (candidate.isClosed()) continue
await candidate.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => {})
if (await candidate.locator('#recoveryPanel.visible').count().catch(() => 0)) return candidate
}
return null
}, 'unsafe recovery selection page')
assert.equal(
await page.locator('#recoveryCode').innerText(),
'desktop_selected_recovery_profile_unsafe',
)
await delay(1_000)
assert.deepEqual(await readdir(outside), ['opensquilla'])
assert.equal(
await readFile(unsafeUpdateState, 'utf8'),
unsafeUpdateBytes,
'updater persistence must not read or rewrite an unsafe selected recovery profile',
)
console.log(JSON.stringify({ ok: true, stableCode: 'desktop_selected_recovery_profile_unsafe' }))
} finally {
await app?.close().catch(() => {})
await rm(root, { recursive: true, force: true }).catch(() => {})
}
@@ -0,0 +1,118 @@
import assert from 'node:assert/strict'
import {
parseOpenSquillaReleaseTag,
selectMacPrereleaseCandidate,
} from '../dist/update-feed-resolver.js'
// This exercises the resolver shipped after Preview 2. It proves that clients
// containing this resolver can select later releases; it does not prove that
// the already-published Preview 1/2 binaries can discover Preview 3.
// --- tag parsing: PEP440 rc, semver rc, stable, and rejects ---
assert.deepEqual(parseOpenSquillaReleaseTag('v0.5.0rc2'), { base: '0.5.0', rc: 2 })
assert.deepEqual(parseOpenSquillaReleaseTag('0.5.0-rc2'), { base: '0.5.0', rc: 2 })
assert.deepEqual(parseOpenSquillaReleaseTag('v0.5.0-rc.3'), { base: '0.5.0', rc: 3 })
assert.deepEqual(parseOpenSquillaReleaseTag('v0.5.0'), { base: '0.5.0', rc: null })
assert.equal(parseOpenSquillaReleaseTag('website-2026-01'), null)
assert.equal(parseOpenSquillaReleaseTag('v0.5'), null)
const withMacFeed = (tag) => ({ tag_name: tag, assets: [{ name: 'latest-mac.yml' }] })
const noMacFeed = (tag) => ({ tag_name: tag, assets: [{ name: 'OpenSquilla-mac.zip' }] })
// 1. A resolver-enabled client on 0.5.0-rc1 sees v0.5.0rc2 (PEP440 tag).
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 1 }, [
withMacFeed('v0.5.0rc2'),
withMacFeed('v0.5.0rc1'),
])
assert.ok(c, 'rc1 should find rc2')
assert.equal(c.tag, 'v0.5.0rc2')
assert.equal(c.version, '0.5.0-rc2')
assert.equal(c.feedUrl, 'https://github.com/opensquilla/opensquilla/releases/download/v0.5.0rc2')
}
// 2. A resolver-enabled client on 0.5.0-rc2 sees v0.5.0rc3.
{
const c = selectMacPrereleaseCandidate(
{ base: '0.5.0', rc: 2 },
[withMacFeed('v0.5.0rc3'), withMacFeed('v0.5.0rc2')],
)
assert.ok(c)
assert.equal(c.tag, 'v0.5.0rc3')
assert.equal(c.version, '0.5.0-rc3')
}
// 3. 0.5.0-rc2 sees the final stable v0.5.0 (stable outranks a later rc).
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 2 }, [
withMacFeed('v0.5.0'),
withMacFeed('v0.5.0rc3'),
withMacFeed('v0.5.0rc2'),
])
assert.ok(c, 'rc2 should find a candidate')
assert.equal(c.tag, 'v0.5.0')
assert.equal(c.version, '0.5.0')
}
// 2b. Two-digit rc ordering is numeric, not string: rc9 sees rc10 (not the
// reverse). electron-updater's own semver gate sorts rc10 below rc9, which is
// why the resolver path also sets allowDowngrade — see main.ts.
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 9 }, [
withMacFeed('v0.5.0rc10'),
withMacFeed('v0.5.0rc9'),
])
assert.ok(c, 'rc9 should find rc10')
assert.equal(c.tag, 'v0.5.0rc10')
assert.equal(c.version, '0.5.0-rc10')
}
// rc10 does not regress to rc9.
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 10 }, [
withMacFeed('v0.5.0rc10'),
withMacFeed('v0.5.0rc9'),
])
assert.equal(c, null, 'rc10 must not pick the lower rc9')
}
// 4. A prerelease does NOT jump to a different base's preview (0.5.0-rc2 ignores v0.6.0rc1).
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 2 }, [
withMacFeed('v0.6.0rc1'),
withMacFeed('v0.5.0rc2'),
])
assert.equal(c, null, 'rc2 must not cross to a different base')
}
// 4a. A newer same-base release without latest-mac.yml is skipped.
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 1 }, [noMacFeed('v0.5.0rc2')])
assert.equal(c, null, 'candidate without latest-mac.yml is skipped')
}
// 4b. When the highest release lacks the feed, fall back to the highest that has it.
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 1 }, [
noMacFeed('v0.5.0rc3'),
withMacFeed('v0.5.0rc2'),
])
assert.ok(c, 'should fall back to rc2 which has the feed')
assert.equal(c.tag, 'v0.5.0rc2')
}
// 5. No newer same-base release → no candidate (current rc is the latest).
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 2 }, [withMacFeed('v0.5.0rc2')])
assert.equal(c, null, 'only the current rc exists → up to date')
}
// 6. Draft releases are ignored.
{
const c = selectMacPrereleaseCandidate({ base: '0.5.0', rc: 1 }, [
{ tag_name: 'v0.5.0rc2', draft: true, assets: [{ name: 'latest-mac.yml' }] },
])
assert.equal(c, null, 'draft releases are not upgrade candidates')
}
console.log('Update resolver tests passed.')
@@ -0,0 +1,47 @@
import { existsSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const packageJsonPath = join(packageRoot, 'package.json')
const macIconPath = join(packageRoot, 'assets', 'icon.icns')
const windowsIconPath = join(packageRoot, 'assets', 'icon.ico')
const failures = []
function fail(message) {
failures.push(message)
}
function expectEqual(actual, expected, label) {
if (actual !== expected) {
fail(`${label} must be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`)
}
}
const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8'))
const build = pkg.build ?? {}
if (!existsSync(macIconPath)) {
fail(`macOS icon is missing at ${macIconPath}`)
}
if (!existsSync(windowsIconPath)) {
fail(`Windows icon is missing at ${windowsIconPath}`)
}
expectEqual(build.mac?.icon, 'assets/icon.icns', 'build.mac.icon')
expectEqual(build.dmg?.icon, 'assets/icon.icns', 'build.dmg.icon')
expectEqual(build.win?.icon, 'assets/icon.ico', 'build.win.icon')
expectEqual(build.nsis?.installerIcon, 'assets/icon.ico', 'build.nsis.installerIcon')
expectEqual(build.nsis?.uninstallerIcon, 'assets/icon.ico', 'build.nsis.uninstallerIcon')
if (failures.length > 0) {
console.error('OpenSquilla desktop icon verification failed:')
for (const failure of failures) console.error(`- ${failure}`)
process.exit(1)
}
console.log('OpenSquilla desktop icon verification passed.')
+400
View File
@@ -0,0 +1,400 @@
import { spawnSync } from 'node:child_process'
import { existsSync, statSync } from 'node:fs'
import { readdir, readFile, stat } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
const scriptDir = dirname(fileURLToPath(import.meta.url))
const packageRoot = resolve(scriptDir, '..')
const repoRoot = resolve(packageRoot, '..', '..')
const runtimeGatewayDir = join(packageRoot, 'runtime', 'gateway')
const sourceMainPath = join(packageRoot, 'src', 'main.ts')
const compiledMainPath = join(packageRoot, 'dist', 'main.js')
const packageJsonPath = join(packageRoot, 'package.json')
const desktopOutputDir = join(repoRoot, 'dist', 'desktop-electron')
const failures = []
function fail(message) {
failures.push(message)
}
function pathIsFileSync(path) {
try {
return statSync(path).isFile()
} catch {
return false
}
}
function gatewayBinary(root, platform) {
const binaryName = platform === 'win32' ? 'opensquilla-gateway.exe' : 'opensquilla-gateway'
const candidates = [join(root, 'opensquilla-gateway', binaryName), join(root, binaryName)]
const binary = candidates.find(pathIsFileSync)
return { binary, candidates }
}
function verificationEnv() {
const keys = [
'PATH',
'Path',
'HOME',
'USERPROFILE',
'TMPDIR',
'TEMP',
'TMP',
'SystemRoot',
'ComSpec',
'PATHEXT',
'LANG',
'LC_ALL',
]
const env = {
PYTHONUNBUFFERED: '1',
PYTHONUTF8: '1',
PYTHONIOENCODING: 'utf-8:replace',
}
for (const key of keys) {
if (process.env[key] !== undefined) env[key] = process.env[key]
}
return env
}
function outputTail(output) {
const tail = [output.stdout, output.stderr]
.filter(Boolean)
.join('\n')
.trim()
.split(/\r?\n/)
.slice(-12)
.join('\n')
.trim()
return tail ? `\nOutput tail:\n${tail}` : ''
}
function requireGatewayBinary(root, label, platform) {
const { binary, candidates } = gatewayBinary(root, platform)
if (!binary) {
fail(`${label} gateway binary is missing; checked ${candidates.join(', ')}`)
return null
}
return binary
}
function verifyGatewayCommand(binary, label, args, options = {}) {
const result = spawnSync(binary, args, {
cwd: dirname(binary),
encoding: 'utf8',
env: verificationEnv(),
input: options.input,
timeout: options.timeout ?? 30000,
windowsHide: true,
})
const commandLabel = `${label} gateway command ${args.join(' ')}`
if (result.error) {
fail(`${commandLabel} could not start: ${result.error.message}${outputTail(result)}`)
return
}
if (result.status !== 0) {
const exitReason = result.signal ? `signal ${result.signal}` : `exit ${result.status}`
fail(`${commandLabel} failed with ${exitReason}${outputTail(result)}`)
}
}
function verifyMacLightgbmRuntime(files, label) {
const lightgbmLibs = files.filter((path) => path.endsWith(join('lightgbm', 'lib', 'lib_lightgbm.dylib')))
if (lightgbmLibs.length === 0) {
fail(`${label} runtime is missing lightgbm/lib/lib_lightgbm.dylib`)
return
}
for (const lightgbmLib of lightgbmLibs) {
const bundledLibomp = join(dirname(lightgbmLib), 'libomp.dylib')
if (!files.includes(bundledLibomp)) {
fail(`${label} runtime is missing bundled libomp.dylib next to ${lightgbmLib}`)
}
if (process.platform !== 'darwin') continue
const result = spawnSync('otool', ['-L', lightgbmLib], {
encoding: 'utf8',
windowsHide: true,
})
if (result.error) {
fail(`${label} could not inspect ${lightgbmLib} with otool: ${result.error.message}`)
} else if (result.status !== 0) {
fail(`${label} otool -L failed for ${lightgbmLib}${outputTail(result)}`)
} else if (!result.stdout.includes('@loader_path/libomp.dylib')) {
fail(`${label} ${lightgbmLib} does not load libomp.dylib via @loader_path`)
}
}
}
async function listFiles(root) {
const files = []
async function walk(dir) {
let entries
try {
entries = await readdir(dir, { withFileTypes: true })
} catch (error) {
fail(`${root} could not be read: ${error instanceof Error ? error.message : String(error)}`)
return
}
for (const entry of entries) {
const path = join(dir, entry.name)
if (entry.isDirectory()) {
await walk(path)
} else if (entry.isFile() && entry.name !== '.DS_Store') {
files.push(path)
}
}
}
await walk(root)
return files
}
async function verifyRuntime(root, label, { platform, executeCommands }) {
if (!existsSync(root)) {
fail(`${label} runtime is missing at ${root}`)
return
}
const info = await stat(root).catch(() => null)
if (!info?.isDirectory()) {
fail(`${label} runtime is not a directory: ${root}`)
return
}
const files = await listFiles(root)
if (files.length === 0) {
fail(`${label} runtime is empty: ${root}`)
return
}
const compatFile = files.find((path) => path.endsWith(join('opensquilla', 'compat', 'aiosqlite.py')))
if (!compatFile) {
fail(`${label} runtime is missing opensquilla/compat/aiosqlite.py`)
} else {
const source = await readFile(compatFile, 'utf8')
if (!source.includes('async def create_function(') || !source.includes('self._conn.create_function')) {
fail(`${label} runtime aiosqlite.py does not contain _AsyncConnection.create_function`)
}
}
const binary = requireGatewayBinary(root, label, platform)
if (executeCommands) {
if (!binary) return
verifyGatewayCommand(binary, label, ['--help'])
verifyGatewayCommand(binary, label, ['code-task', '--help'])
verifyGatewayCommand(binary, label, ['code-task', 'stage-task-file'], { input: 'desktop package smoke\n' })
verifyGatewayCommand(binary, label, ['code-task', 'smoke-imports'], { timeout: 120000 })
verifyGatewayCommand(binary, label, ['code-task', 'smoke-router'], { timeout: 120000 })
}
if (platform === 'darwin') {
verifyMacLightgbmRuntime(files, label)
}
}
function verifyMainProcess(source, label) {
for (const expected of [
'gatewayStartPromise',
'openOrResumeDesktopApp',
'ensureGatewayStarted',
'isCurrentWindowAtControlUi',
]) {
if (!source.includes(expected)) fail(`${label} main process is missing ${expected}`)
}
const helperIndex = source.indexOf('async function openOrResumeDesktopApp')
const createIndex = source.indexOf('await createMainWindow()', helperIndex)
const ensureIndex = source.indexOf('ensureGatewayStarted()', helperIndex)
if (helperIndex === -1 || createIndex === -1 || ensureIndex === -1 || createIndex > ensureIndex) {
fail(`${label} main process does not create the desktop window before gateway startup`)
}
if (!/app\.on\(['"]activate['"][\s\S]{0,240}openOrResumeDesktopApp/.test(source)) {
fail(`${label} main process activate handler does not route through openOrResumeDesktopApp`)
}
if (!/second-instance[\s\S]{0,240}openOrResumeDesktopApp/.test(source)) {
fail(`${label} main process second-instance handler does not route through openOrResumeDesktopApp`)
}
const loadCurrentIndex = source.indexOf('async function loadControlUiIntoCurrentWindow')
const controlGuardIndex = source.indexOf('isCurrentWindowAtControlUi(window, gatewayUrl)', loadCurrentIndex)
const controlLoadIndex = source.indexOf('loadControlUi(window, gatewayUrl)', loadCurrentIndex)
if (
loadCurrentIndex === -1
|| controlGuardIndex === -1
|| controlLoadIndex === -1
|| controlGuardIndex > controlLoadIndex
) {
fail(`${label} main process reloads the Control UI before checking whether it is already loaded`)
}
const onboardingIndex = source.indexOf('async function runOnboarding')
const onboardingWindowIndex = source.indexOf('onboardingWindow = new BrowserWindow', onboardingIndex)
const parentIndex = source.indexOf('const parentWindow = currentMainWindow()', onboardingIndex)
const parentOptionIndex = source.indexOf('parent: parentWindow ?? undefined', onboardingWindowIndex)
const modalOptionIndex = source.indexOf('modal: Boolean(parentWindow)', onboardingWindowIndex)
if (
onboardingIndex === -1
|| onboardingWindowIndex === -1
|| parentIndex === -1
|| parentOptionIndex === -1
|| modalOptionIndex === -1
|| parentIndex > onboardingWindowIndex
) {
fail(`${label} main process does not make first-run onboarding an owned modal child window`)
}
const focusIndex = source.indexOf('function focusMainWindow')
const focusSource = focusIndex === -1 ? '' : source.slice(focusIndex, focusIndex + 800)
const onboardingFocusMatch = /if\s*\(\s*focusOnboardingWindow\(\)\s*\)\s*(?:\{\s*)?return true\s*;?/.exec(focusSource)
const mainFocusMatch =
/if\s*\(\s*!mainWindow\s*\|\|\s*mainWindow\.isDestroyed\(\)\s*\)\s*(?:\{\s*)?return false\s*;?/.exec(
focusSource,
)
if (
focusIndex === -1
|| !onboardingFocusMatch
|| !mainFocusMatch
|| onboardingFocusMatch.index > mainFocusMatch.index
) {
fail(`${label} main process does not prefer the onboarding window when focusing`)
}
}
async function verifySourceMain() {
if (!existsSync(sourceMainPath)) {
fail(`source Electron main process is missing at ${sourceMainPath}`)
return
}
verifyMainProcess(await readFile(sourceMainPath, 'utf8'), 'source')
}
async function verifyInstallerDataPolicy() {
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))
if (packageJson.build?.nsis?.deleteAppDataOnUninstall !== false) {
fail('NSIS uninstall must preserve Desktop profile data (deleteAppDataOnUninstall=false)')
}
}
async function verifyCompiledMain() {
if (!existsSync(compiledMainPath)) {
fail(`compiled Electron main process is missing at ${compiledMainPath}; run npm run build first`)
return
}
verifyMainProcess(await readFile(compiledMainPath, 'utf8'), 'compiled')
}
async function findGeneratedBundles(root) {
const bundles = []
const seenResourcesDirs = new Set()
if (!existsSync(root)) return bundles
function addBundle(bundle) {
if (seenResourcesDirs.has(bundle.resourcesDir)) return
seenResourcesDirs.add(bundle.resourcesDir)
bundles.push(bundle)
}
async function walk(dir, depth) {
if (depth > 5) return
const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
for (const entry of entries) {
if (!entry.isDirectory()) continue
const path = join(dir, entry.name)
if (entry.name.endsWith('.app')) {
addBundle({
label: `app bundle ${path}`,
resourcesDir: join(path, 'Contents', 'Resources'),
platform: 'darwin',
})
} else if (entry.name === 'win-unpacked' || entry.name === 'linux-unpacked') {
addBundle({
label: `generated bundle ${path}`,
resourcesDir: join(path, 'resources'),
platform: entry.name === 'win-unpacked' ? 'win32' : 'linux',
})
} else {
await walk(path, depth + 1)
}
}
}
await walk(root, 0)
return bundles
}
async function readAsarText(asarPath, innerPath) {
const asar = await import('@electron/asar')
return asar.extractFile(asarPath, innerPath).toString('utf8')
}
async function verifyAsarPackageVersion(asarPath, label) {
let packageJson
try {
packageJson = JSON.parse(await readAsarText(asarPath, 'package.json'))
} catch (error) {
fail(`${label} app.asar package.json could not be inspected: ${error instanceof Error ? error.message : String(error)}`)
return
}
const version = typeof packageJson.version === 'string' ? packageJson.version : ''
const semverPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
if (!semverPattern.test(version) || /\d(?:a|b|rc)\d+$/.test(version)) {
fail(
`${label} app.asar package.json version is not npm semver: ${JSON.stringify(version)}; ` +
'prereleases must use 0.5.0-rc2 style, not 0.5.0rc2'
)
}
}
async function verifyGeneratedBundle({ label, resourcesDir, platform }) {
await verifyRuntime(join(resourcesDir, 'runtime', 'gateway'), label, {
platform,
executeCommands: platform === process.platform,
})
const asarPath = join(resourcesDir, 'app.asar')
if (!existsSync(asarPath)) {
fail(`${label} is missing app.asar`)
return
}
try {
await verifyAsarPackageVersion(asarPath, label)
verifyMainProcess(await readAsarText(asarPath, 'dist/main.js'), label)
} catch (error) {
fail(`${label} app.asar could not be inspected: ${error instanceof Error ? error.message : String(error)}`)
}
}
await verifyRuntime(runtimeGatewayDir, 'source', { platform: process.platform, executeCommands: true })
await verifyInstallerDataPolicy()
await verifySourceMain()
await verifyCompiledMain()
const generatedBundles = await findGeneratedBundles(desktopOutputDir)
for (const bundle of generatedBundles) {
await verifyGeneratedBundle(bundle)
}
if (failures.length > 0) {
console.error('OpenSquilla desktop package verification failed:')
for (const failure of failures) console.error(`- ${failure}`)
process.exit(1)
}
console.log('OpenSquilla desktop package verification passed.')