feat: ship skill and MCP workflows with signed updater for v0.0.7

This commit is contained in:
xiaohan.yuan committed 2026-10-21 00:04:17 +08:00
1 parent 1b52df0603
commit a01085c656
136 files changed
+18646 -217

No files matched your search

+666
View File
@@ -0,0 +1,666 @@
#!/usr/bin/env node
'use strict'
const assert = require('node:assert/strict')
const crypto = require('node:crypto')
const fs = require('node:fs')
const fsp = require('node:fs/promises')
const http = require('node:http')
const net = require('node:net')
const os = require('node:os')
const path = require('node:path')
const { execFileSync, spawn } = require('node:child_process')
const ROOT = path.resolve(__dirname, '..')
const MANIFEST_URL = 'http://192.168.10.6/ycode/releases/latest.json'
const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
function readExpectedVersion() {
const args = process.argv.slice(2)
let value = null
for (let index = 0; index < args.length; index += 1) {
const argument = args[index]
if (argument === '--expected-version') {
value = args[++index]
} else if (argument.startsWith('--expected-version=')) {
value = argument.slice('--expected-version='.length)
} else {
throw new Error(`Unknown argument: ${argument}`)
}
}
if (value === null) value = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version
if (typeof value !== 'string' || !SEMVER_RE.test(value)) throw new Error(`Invalid expected version: ${value}`)
return value
}
const EXPECTED_VERSION = readExpectedVersion()
const PACKAGED_EXE = path.resolve(process.env.YCODE_QA_PACKAGED_EXE || path.join(ROOT, 'release', 'win-unpacked', 'YCode.exe'))
const PUBLIC_KEY = path.join(ROOT, 'resources', 'updates', 'public-key.pem')
const OUTPUT_ROOT = path.join(ROOT, '.tmp', 'updates-live')
const STAMP = new Date().toISOString().replace(/[:.]/g, '-')
const RUN_ROOT = path.join(OUTPUT_ROOT, STAMP)
const REPORT_PATH = path.join(RUN_ROOT, 'report.json')
const LATEST_REPORT_PATH = path.join(OUTPUT_ROOT, 'latest-report.json')
const SCREENSHOT_PATH = path.join(RUN_ROOT, 'production-boot.png')
const REGISTRY_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\8a4ee941-d893-569b-8bcb-2a55d270c04b'
const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds))
let activeBootChild = null
async function handleInterrupt(signal) {
await stopExactChild(activeBootChild)
process.exit(signal === 'SIGINT' ? 130 : 143)
}
process.once('SIGINT', () => void handleInterrupt('SIGINT'))
process.once('SIGTERM', () => void handleInterrupt('SIGTERM'))
function sha256(value) {
return crypto.createHash('sha256').update(value).digest('hex')
}
async function sha256File(filename) {
const hash = crypto.createHash('sha256')
for await (const chunk of fs.createReadStream(filename)) hash.update(chunk)
return hash.digest('hex')
}
async function writeJsonAtomic(filename, value) {
await fsp.mkdir(path.dirname(filename), { recursive: true })
const temporary = `${filename}.${process.pid}.tmp`
await fsp.writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
await fsp.rename(temporary, filename)
}
function errorDetails(error) {
if (error instanceof Error) return { name: error.name, message: error.message, stack: error.stack }
return { name: 'Error', message: String(error) }
}
async function fetchBytes(url, maximum = 1024 * 1024, timeoutMs = 30_000) {
let current = new URL(url)
const origin = current.origin
for (let redirects = 0; redirects <= 5; redirects += 1) {
const response = await fetch(current, {
redirect: 'manual',
cache: 'no-store',
signal: AbortSignal.timeout(timeoutMs)
})
if ([301, 302, 303, 307, 308].includes(response.status)) {
const location = response.headers.get('location')
await response.body?.cancel().catch(() => undefined)
if (!location || redirects === 5) throw new Error('Manifest redirect chain is invalid')
const next = new URL(location, current)
if (next.origin !== origin) throw new Error('Manifest redirect left the configured origin')
current = next
continue
}
if (!response.ok) throw new Error(`Manifest request failed with HTTP ${response.status}`)
const declared = Number(response.headers.get('content-length') || 0)
if (declared > maximum) throw new Error('Manifest response exceeds the size limit')
const bytes = Buffer.from(await response.arrayBuffer())
if (bytes.length > maximum) throw new Error('Manifest response exceeds the size limit')
return bytes
}
throw new Error('Manifest redirect chain is invalid')
}
async function reserveLoopbackPort() {
const server = net.createServer()
await new Promise((resolve, reject) => {
server.once('error', reject)
server.listen(0, '127.0.0.1', resolve)
})
const address = server.address()
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()))
if (!address || typeof address === 'string') throw new Error('Unable to reserve a debug port')
return address.port
}
function getJson(url, timeoutMs = 2_000) {
return new Promise((resolve, reject) => {
const request = http.get(url, { timeout: timeoutMs, family: 4 }, response => {
const chunks = []
response.on('data', chunk => chunks.push(chunk))
response.on('end', () => {
if (response.statusCode !== 200) {
reject(new Error(`HTTP ${response.statusCode} from ${url}`))
return
}
try {
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')))
} catch (error) {
reject(error)
}
})
})
request.on('timeout', () => request.destroy(new Error('CDP endpoint timed out')))
request.on('error', reject)
})
}
async function waitForCdpPage(port, child) {
const deadline = Date.now() + 45_000
while (Date.now() < deadline) {
if (child.exitCode !== null) throw new Error(`Packaged app exited early with code ${child.exitCode}`)
try {
const targets = await getJson(`http://127.0.0.1:${port}/json/list`)
const page = targets.find(target => target.type === 'page'
&& typeof target.webSocketDebuggerUrl === 'string'
&& (/\/index\.html(?:[?#]|$)/.test(target.url) || /\/$/.test(target.url)))
if (page) return page
} catch {
// Chromium's endpoint is not ready yet.
}
await sleep(250)
}
throw new Error('Timed out waiting for the packaged app renderer target')
}
function openWebSocket(url) {
const WebSocketCtor = globalThis.WebSocket || require('ws')
return new Promise((resolve, reject) => {
const socket = new WebSocketCtor(url)
const timer = setTimeout(() => reject(new Error('CDP WebSocket open timed out')), 15_000)
const cleanup = () => {
clearTimeout(timer)
socket.removeEventListener?.('open', onOpen)
socket.removeEventListener?.('error', onError)
}
const onOpen = () => {
cleanup()
resolve(socket)
}
const onError = error => {
cleanup()
reject(error instanceof Error ? error : new Error('CDP WebSocket failed'))
}
socket.addEventListener('open', onOpen)
socket.addEventListener('error', onError)
})
}
class CdpClient {
constructor(socket) {
this.socket = socket
this.nextId = 1
this.pending = new Map()
socket.addEventListener('message', event => {
let message
try {
const text = typeof event.data === 'string' ? event.data : Buffer.from(event.data).toString('utf8')
message = JSON.parse(text)
} catch {
return
}
if (!message.id) return
const pending = this.pending.get(message.id)
if (!pending) return
this.pending.delete(message.id)
clearTimeout(pending.timer)
if (message.error) pending.reject(new Error(`${message.error.message || 'CDP error'} (${message.error.code})`))
else pending.resolve(message.result)
})
socket.addEventListener('close', () => {
for (const pending of this.pending.values()) {
clearTimeout(pending.timer)
pending.reject(new Error('CDP WebSocket closed'))
}
this.pending.clear()
})
}
static async connect(url) {
return new CdpClient(await openWebSocket(url))
}
request(method, params = {}, timeoutMs = 30_000) {
const id = this.nextId
this.nextId += 1
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this.pending.delete(id)
reject(new Error(`CDP request timed out: ${method}`))
}, timeoutMs)
this.pending.set(id, { resolve, reject, timer })
this.socket.send(JSON.stringify({ id, method, params }))
})
}
async evaluate(expression) {
const result = await this.request('Runtime.evaluate', {
expression,
awaitPromise: true,
returnByValue: true,
userGesture: false
})
if (result.exceptionDetails) {
const description = result.exceptionDetails.exception?.description
|| result.exceptionDetails.text
|| 'Renderer evaluation failed'
throw new Error(description)
}
return result.result?.value
}
close() {
try {
this.socket.close()
} catch {
// The renderer may already be gone.
}
}
}
async function waitForUpdatesApi(client, child) {
const deadline = Date.now() + 30_000
while (Date.now() < deadline) {
if (child.exitCode !== null) throw new Error(`Packaged app exited early with code ${child.exitCode}`)
try {
const ready = await client.evaluate(
"typeof window.ycode?.updates?.getState === 'function' && document.readyState === 'complete'"
)
if (ready === true) return
} catch {
// React/preload may still be starting.
}
await sleep(250)
}
throw new Error('Timed out waiting for window.ycode.updates')
}
const UPDATER_INSPECTION_EXPRESSION = `(async () => {
const api = window.ycode?.updates
if (!api) throw new Error('window.ycode.updates is unavailable')
const apiMethods = ['getState', 'check', 'download', 'install', 'dismiss', 'openDownloadPage', 'openLog', 'onState']
.filter(name => typeof api[name] === 'function')
const before = await api.getState()
const events = []
const unsubscribe = api.onState(state => events.push(state))
let after
try {
after = await api.check()
const deadline = Date.now() + 15000
while (Date.now() < deadline) {
if (events.some(state => state.status === 'up-to-date')) break
if (events.some(state => state.status === 'error' || state.status === 'unsupported')) break
await new Promise(resolve => setTimeout(resolve, 100))
}
await new Promise(resolve => setTimeout(resolve, 500))
} finally {
unsubscribe()
}
return { apiMethods, before, after, events }
})()`
async function captureScreenshot(client, filename) {
await client.request('Page.enable')
const screenshot = await client.request('Page.captureScreenshot', {
format: 'png',
fromSurface: true,
captureBeyondViewport: false
})
await fsp.writeFile(filename, Buffer.from(screenshot.data, 'base64'))
}
async function stopExactChild(child) {
if (!child || child.exitCode !== null || child.signalCode !== null) return
const exited = new Promise(resolve => child.once('exit', resolve))
child.kill()
await Promise.race([exited, sleep(5_000)])
if (child.exitCode === null && child.signalCode === null) {
child.kill('SIGKILL')
await Promise.race([exited, sleep(2_000)])
}
}
async function runProductionBoot(expectedManifest) {
if (!fs.existsSync(PACKAGED_EXE)) throw new Error(`Packaged executable is missing: ${PACKAGED_EXE}`)
const port = await reserveLoopbackPort()
const userData = path.join(RUN_ROOT, 'production-boot-user-data')
await fsp.mkdir(userData, { recursive: true })
const stdoutPath = path.join(RUN_ROOT, 'production-boot.stdout.log')
const stderrPath = path.join(RUN_ROOT, 'production-boot.stderr.log')
const stdout = fs.createWriteStream(stdoutPath, { flags: 'w' })
const stderr = fs.createWriteStream(stderrPath, { flags: 'w' })
const env = {
...process.env,
YCODE_USER_DATA_DIR: userData,
YCODE_SKIP_KERNEL_DATA_RESET: '1',
YCODE_QA_OFFSCREEN: '1'
}
delete env.ELECTRON_RENDERER_URL
delete env.YCODE_QA_DIRECTORY
for (const key of ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'YCODE_API_KEY', 'CODEBUDDY_API_KEY']) delete env[key]
const child = spawn(PACKAGED_EXE, [
`--remote-debugging-port=${port}`,
'--remote-debugging-address=127.0.0.1'
], {
cwd: RUN_ROOT,
env,
windowsHide: true,
stdio: ['ignore', 'pipe', 'pipe']
})
child.stdout.pipe(stdout)
child.stderr.pipe(stderr)
activeBootChild = child
let client
let inspection
let screenshotWritten = false
try {
if (!child.pid) throw new Error('Packaged app did not expose a child PID')
const page = await waitForCdpPage(port, child)
client = await CdpClient.connect(page.webSocketDebuggerUrl)
await waitForUpdatesApi(client, child)
inspection = await client.evaluate(UPDATER_INSPECTION_EXPRESSION)
await captureScreenshot(client, SCREENSHOT_PATH)
screenshotWritten = true
assert.equal(inspection.before.currentVersion, EXPECTED_VERSION)
assert.equal(inspection.after.currentVersion, EXPECTED_VERSION)
assert.equal(inspection.after.status, 'up-to-date')
assert.equal(inspection.after.lastExplicit, true)
assert.equal(inspection.apiMethods.length, 8)
assert.equal(inspection.events.some(state => state.status === 'checking'), true)
assert.equal(inspection.events.some(state => state.status === 'up-to-date'), true)
assert.equal(inspection.events.some(state => ['downloading', 'ready', 'installing'].includes(state.status)), false)
assert.equal(inspection.events.every(state => state.currentVersion === EXPECTED_VERSION), true)
return {
mode: 'actual-production-packaged-boot',
executable: PACKAGED_EXE,
childPid: child.pid,
debugPort: port,
isolatedUserData: userData,
expectedServerVersion: expectedManifest.version,
apiMethods: inspection.apiMethods,
before: inspection.before,
after: inspection.after,
sharedIpcEvents: inspection.events,
screenshot: screenshotWritten ? SCREENSHOT_PATH : null,
stdoutLog: stdoutPath,
stderrLog: stderrPath,
installerOrUpgradeRequested: false,
passed: true
}
} finally {
client?.close()
await stopExactChild(child)
if (activeBootChild === child) activeBootChild = null
stdout.end()
stderr.end()
}
}
function createRuntimeEntry(filename) {
const relative = value => `./${path.relative(path.dirname(filename), value).replace(/\\/g, '/')}`
return [
`export { UpdateManager } from ${JSON.stringify(relative(path.join(ROOT, 'src/main/updates/manager.ts')))}`,
`export { FileUpdatePersistence } from ${JSON.stringify(relative(path.join(ROOT, 'src/main/updates/storage.ts')))}`,
`export { verifySignedManifest } from ${JSON.stringify(relative(path.join(ROOT, 'src/main/updates/manifest.ts')))}`,
''
].join('\n')
}
function bundleActualManager(runtimeDirectory) {
fs.mkdirSync(runtimeDirectory, { recursive: true })
const entry = path.join(runtimeDirectory, 'entry.ts')
const output = path.join(runtimeDirectory, 'updater-runtime.cjs')
fs.writeFileSync(entry, createRuntimeEntry(entry), 'utf8')
const esbuild = path.join(ROOT, 'node_modules', 'esbuild', 'bin', 'esbuild')
if (!fs.existsSync(esbuild)) throw new Error(`esbuild is missing: ${esbuild}`)
execFileSync(process.execPath, [
esbuild,
entry,
'--bundle',
'--platform=node',
'--format=cjs',
'--target=node22',
`--outfile=${output}`,
'--log-level=warning'
], { cwd: ROOT, stdio: 'inherit' })
return { entry, output, runtime: require(output) }
}
function forbiddenInstaller() {
const calls = { validateEnvironment: 0, verifyInstaller: 0, launch: 0 }
const fail = name => {
calls[name] += 1
throw new Error('Live verification is download-only; installer access is forbidden')
}
return {
calls,
adapter: {
validateEnvironment: async () => fail('validateEnvironment'),
verifyInstaller: async () => fail('verifyInstaller'),
launch: async () => fail('launch')
}
}
}
async function runSimulatedPredecessorDownload(expectedManifest, runtime, publicKeyPem) {
const directory = path.join(RUN_ROOT, 'simulated-predecessor')
const cacheDirectory = path.join(directory, 'cache')
const persistence = new runtime.FileUpdatePersistence(path.join(directory, 'state.json'))
const forbidden = forbiddenInstaller()
const callbacks = { beforeInstall: 0, quitForUpdate: 0, installFailed: 0 }
const events = []
const manager = new runtime.UpdateManager({
currentVersion: '0.0.1',
manifestUrl: MANIFEST_URL,
cacheDirectory,
logFile: path.join(directory, 'updater.log'),
persistence,
publicKey: async () => publicKeyPem,
supported: true,
installer: forbidden.adapter,
beforeInstall: async () => {
callbacks.beforeInstall += 1
throw new Error('beforeInstall is forbidden in live download verification')
},
quitForUpdate: async () => {
callbacks.quitForUpdate += 1
throw new Error('quitForUpdate is forbidden in live download verification')
},
installFailed: () => {
callbacks.installFailed += 1
},
autoDownload: false,
manifestTimeoutMs: 60_000,
downloadTimeoutMs: 20 * 60_000
})
let lastLoggedProgress = -1
const unsubscribe = manager.subscribe(state => {
events.push(state)
if (state.status === 'downloading' && typeof state.progress === 'number') {
const bucket = Math.floor(state.progress / 10) * 10
if (bucket > lastLoggedProgress) {
lastLoggedProgress = bucket
console.log(`simulated predecessor download: ${state.progress}%`)
}
}
})
try {
const checked = await manager.check(true)
assert.equal(checked.status, 'available')
assert.equal(checked.version, expectedManifest.version)
const downloaded = await manager.download(true)
assert.equal(downloaded.status, 'ready')
assert.equal(downloaded.progress, 100)
assert.equal(downloaded.currentVersion, '0.0.1')
assert.equal(downloaded.version, expectedManifest.version)
assert.equal(downloaded.lastExplicit, true)
const filename = `YCode-${expectedManifest.version}-Setup.exe`
const cachedPath = path.join(cacheDirectory, filename)
const info = await fsp.stat(cachedPath)
const digest = await sha256File(cachedPath)
assert.equal(info.isFile(), true)
assert.equal(info.size, expectedManifest.size)
assert.equal(digest, expectedManifest.sha256)
assert.equal(path.basename(cachedPath), filename)
const persisted = await persistence.load()
assert.equal(persisted.unfinishedVersion, undefined)
assert.equal(persisted.target, undefined)
assert.deepEqual(forbidden.calls, { validateEnvironment: 0, verifyInstaller: 0, launch: 0 })
assert.deepEqual(callbacks, { beforeInstall: 0, quitForUpdate: 0, installFailed: 0 })
assert.equal(events.some(state => state.status === 'downloading'), true)
return {
mode: 'simulated-predecessor-real-network-download',
simulatedPredecessorVersion: '0.0.1',
actualApplicationVersionChanged: false,
manifestUrl: MANIFEST_URL,
expectedVersion: expectedManifest.version,
state: downloaded,
observedDownloading: true,
cachedPath,
size: info.size,
sha256: digest,
installerAdapterCalls: forbidden.calls,
installCallbacks: callbacks,
passed: true
}
} finally {
unsubscribe()
manager.dispose()
}
}
function readRealUserState() {
let registryFingerprint = null
let registryEntryPresent = false
try {
const output = execFileSync('reg.exe', ['query', REGISTRY_KEY], {
encoding: 'utf8',
windowsHide: true,
timeout: 10_000,
maxBuffer: 64 * 1024
})
registryFingerprint = sha256(output)
registryEntryPresent = true
} catch {
// The current user may not have a production installation.
}
let installedExeFingerprint = null
let installedExePresent = false
const installedExe = path.join(process.env.LOCALAPPDATA || '', 'Programs', 'YCode', 'YCode.exe')
try {
const info = fs.statSync(installedExe)
installedExePresent = true
installedExeFingerprint = sha256(`${installedExe}|${info.size}|${info.mtimeMs}`)
} catch {
// Optional read-only check only.
}
return { registryEntryPresent, registryFingerprint, installedExePresent, installedExeFingerprint }
}
async function main() {
await fsp.mkdir(RUN_ROOT, { recursive: true })
const realUserStateBefore = readRealUserState()
const report = {
schemaVersion: 1,
startedAt: new Date().toISOString(),
finishedAt: null,
manifestUrl: MANIFEST_URL,
expectedVersion: EXPECTED_VERSION,
realApplicationUpgradePerformed: false,
installerLaunched: false,
checks: {
productionPackagedBoot: null,
simulatedPredecessorDownload: null
},
realUserStateBefore,
realUserStateAfter: null,
limitations: [
'Production boot verifies only packaged updater IPC and up-to-date state.',
'Installer download uses a simulated predecessor version 0.0.1 manager.',
'No real application upgrade, installer launch, deployment, or replacement is performed.'
]
}
let exitCode = 0
try {
const publicKeyPem = await fsp.readFile(PUBLIC_KEY, 'utf8')
const runtime = bundleActualManager(path.join(RUN_ROOT, 'runtime')).runtime
const manifestBytes = await fetchBytes(MANIFEST_URL)
const manifest = runtime.verifySignedManifest(manifestBytes, {
manifestUrl: MANIFEST_URL,
publicKeyPem
})
assert.equal(manifest.payload.version, EXPECTED_VERSION)
const platform = manifest.payload.platforms['windows-x64']
const expectedManifest = {
version: manifest.payload.version,
url: platform.url,
size: platform.size,
sha256: platform.sha256
}
report.verifiedManifest = expectedManifest
try {
report.checks.productionPackagedBoot = await runProductionBoot(expectedManifest)
} catch (error) {
exitCode = 1
report.checks.productionPackagedBoot = {
mode: 'actual-production-packaged-boot',
passed: false,
error: errorDetails(error)
}
}
try {
report.checks.simulatedPredecessorDownload = await runSimulatedPredecessorDownload(
expectedManifest,
runtime,
publicKeyPem
)
} catch (error) {
exitCode = 1
report.checks.simulatedPredecessorDownload = {
mode: 'simulated-predecessor-real-network-download',
simulatedPredecessorVersion: '0.0.1',
actualApplicationVersionChanged: false,
passed: false,
error: errorDetails(error)
}
}
} catch (error) {
exitCode = 1
report.preflightError = errorDetails(error)
} finally {
report.realUserStateAfter = readRealUserState()
report.realUserStateUnchanged = JSON.stringify(report.realUserStateBefore) === JSON.stringify(report.realUserStateAfter)
report.finishedAt = new Date().toISOString()
report.passed = exitCode === 0
await writeJsonAtomic(REPORT_PATH, report)
await writeJsonAtomic(LATEST_REPORT_PATH, report)
console.log(`report: ${REPORT_PATH}`)
if (fs.existsSync(SCREENSHOT_PATH)) console.log(`screenshot: ${SCREENSHOT_PATH}`)
}
process.exitCode = exitCode
}
main().catch(async error => {
console.error(error)
process.exitCode = 1
try {
await fsp.mkdir(RUN_ROOT, { recursive: true })
await writeJsonAtomic(REPORT_PATH, {
schemaVersion: 1,
startedAt: null,
finishedAt: new Date().toISOString(),
manifestUrl: MANIFEST_URL,
expectedVersion: EXPECTED_VERSION,
fatalError: errorDetails(error),
passed: false,
realApplicationUpgradePerformed: false,
installerLaunched: false
})
} catch {
// Preserve the original failure.
}
})