247 lines
29 KiB
JavaScript
247 lines
29 KiB
JavaScript
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs/promises')
|
|
const path = require('node:path')
|
|
|
|
if (!process.versions.electron) {
|
|
;(async () => {
|
|
const directory = await fs.mkdtemp(path.resolve('.tmp/sandbox-app-'))
|
|
await Promise.all(['data', 'home', 'workspace'].map(name => fs.mkdir(path.join(directory, name))))
|
|
const maintenance = path.join(directory, 'maintenance.mjs')
|
|
await require('esbuild').build({ entryPoints: ['scripts/sandbox-maintain.ts'], bundle: true, platform: 'node', format: 'esm', outfile: maintenance, logLevel: 'silent' })
|
|
require('node:child_process').execFileSync(process.execPath, [maintenance, 'toolchain-add', 'node', '--source', process.execPath, '--state-root', path.join(directory, 'data/sandbox')], { windowsHide: true, timeout: 60_000, stdio: 'pipe' })
|
|
const environment = { ...process.env, YCODE_SANDBOX_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: path.join(directory, 'data'), YCODE_OMP_HOME: path.join(directory, 'home'), YCODE_DISABLE_GPU: '1', YCODE_QA_OFFSCREEN: '1' }
|
|
delete environment.ELECTRON_RUN_AS_NODE
|
|
delete environment.ELECTRON_RENDERER_URL
|
|
const child = require('node:child_process').spawn(require('electron'), ['--no-sandbox', '--disable-gpu-sandbox', __filename], { env: environment, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
let log = ''
|
|
child.stdout.on('data', bytes => { log += bytes; process.stdout.write(bytes) })
|
|
child.stderr.on('data', bytes => { log += bytes })
|
|
const timer = setTimeout(() => child.kill(), 300_000)
|
|
child.once('close', async code => {
|
|
clearTimeout(timer)
|
|
await fs.writeFile(path.join(directory, 'process.log'), log)
|
|
if (code) console.error(log.slice(-5000))
|
|
console.log(`SANDBOX_APP_DIRECTORY ${directory}`)
|
|
process.exitCode = code ?? 1
|
|
})
|
|
})().catch(error => { console.error(error); process.exitCode = 1 })
|
|
} else {
|
|
const { app, BrowserWindow } = require('electron')
|
|
const { createServer } = require('node:http')
|
|
const { pathToFileURL } = require('node:url')
|
|
const directory = process.env.YCODE_SANDBOX_QA_DIRECTORY
|
|
assert.ok(directory && path.dirname(directory) === path.resolve('.tmp') && path.basename(directory).startsWith('sandbox-app-'))
|
|
app.setPath('home', path.join(directory, 'home'))
|
|
BrowserWindow.prototype.show = function () {}
|
|
BrowserWindow.prototype.showInactive = function () {}
|
|
let win, server
|
|
const checks = [], requests = []
|
|
const fixtureGit = (...args) => require('node:child_process').execFileSync(path.resolve('resources/sandbox/win32-x64/git/files/git.exe'),
|
|
['-c', 'core.hooksPath=NUL', '-c', 'core.fsmonitor=false', '-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.invalid', ...args],
|
|
{ cwd: path.join(directory, 'workspace'), windowsHide: true, env: { SystemRoot: process.env.SystemRoot, PATH: path.resolve('resources/sandbox/win32-x64/git/files') + ';' + process.env.SystemRoot + '\\System32', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: 'NUL', GIT_CONFIG_SYSTEM: 'NUL' } })
|
|
const pause = ms => new Promise(done => setTimeout(done, ms))
|
|
const read = source => win.webContents.executeJavaScript(source)
|
|
const invoke = (namespace, method, ...args) => read(`window.ycode[${JSON.stringify(namespace)}][${JSON.stringify(method)}](...${JSON.stringify(args)})`)
|
|
const check = (label, condition = true) => { checks.push({ label, pass: !!condition }); assert.ok(condition, label); console.log(`PASS ${label}`) }
|
|
async function wait(operation, label) {
|
|
for (let i = 0; i < 1000; i++) { const value = await operation(); if (value) return value; await pause(40) }
|
|
throw new Error(`Timeout ${label}`)
|
|
}
|
|
const frame = (body, delta, finish = null) => `data: ${JSON.stringify({ id: 'sandbox-qa', object: 'chat.completion.chunk', model: body.model, choices: [{ index: 0, delta, finish_reason: finish }] })}\n\n`
|
|
;(async () => {
|
|
server = createServer(async (request, response) => {
|
|
if (request.url.startsWith('/git/repo.git/info/refs')) {
|
|
const header = Buffer.from('# service=git-upload-pack\n')
|
|
const advertised = fixtureGit('upload-pack', '--stateless-rpc', '--advertise-refs', path.join(directory, 'workspace/.git'))
|
|
response.writeHead(200, { 'content-type': 'application/x-git-upload-pack-advertisement' })
|
|
response.end(Buffer.concat([Buffer.from((header.length + 4).toString(16).padStart(4, '0')), header, Buffer.from('0000'), advertised])); return
|
|
}
|
|
let raw = ''; for await (const part of request) raw += part
|
|
const body = JSON.parse(raw), messages = body.messages || []
|
|
const prompt = JSON.stringify(messages.filter(message => message.role === 'user').at(-1)?.content || '')
|
|
requests.push({ model: body.model, prompt })
|
|
response.writeHead(200, { 'content-type': 'text/event-stream' })
|
|
const lastUser = messages.findLastIndex(message => message.role === 'user')
|
|
if (!messages.slice(lastUser + 1).some(message => message.role === 'tool')) {
|
|
const hostGitArgs = prompt.includes('HOST_GIT_IMPORT') ? { action: 'import' } : prompt.includes('HOST_GIT_CHANGE') ? { action: 'run', argv: ['commit', '--allow-empty', '-m', 'production-host-git-commit'] }
|
|
: prompt.includes('HOST_GIT_COMMIT') ? { action: 'commit' } : prompt.includes('HOST_GIT_READ') ? { action: 'run', argv: ['log', '-1', '--oneline'] }
|
|
: prompt.includes('HOST_GIT_REMOTE') ? { action: 'ls-remote', url: `http://127.0.0.1:${server.address().port}/git/repo.git` } : undefined
|
|
const workspaceAction = prompt.includes('DRAFT_COMMIT') ? 'commit' : prompt.includes('DRAFT_DISCARD') ? 'discard' : undefined
|
|
const command = prompt.includes('DRAFT_READ') ? 'type draft-only.txt && git log -1 --oneline' : prompt.includes('DRAFT_EXTRA') ? 'echo draft-extra>draft-extra.txt' : prompt.includes('DRAFT_CREATE') ? 'echo draft-only>draft-only.txt && git add draft-only.txt && git commit -q -m production-draft-commit'
|
|
: prompt.includes('GIT_READ') ? 'git log -1 --oneline && git status --short' : prompt.includes('GIT') ? 'git --version && echo git-approved>git-approved.txt && git add git-approved.txt && git commit -q -m production-git-commit' : prompt.includes('READ_BRIDGE') ? 'node read-bridge.cjs' : prompt.includes('CANCEL') ? 'powershell.exe -NoProfile -NonInteractive -Command "Start-Sleep -Seconds 12; Set-Content late.txt late"'
|
|
: prompt.includes('DENY') ? 'echo denied>denied.txt' : prompt.includes('REVOKE') ? 'echo revoked>revoked.txt' : prompt.includes('NODE') ? 'node --require "%YCODE_SANDBOX_PROCESS%" -e "const fs=require(\'fs\'),cp=require(\'child_process\');fs.writeFileSync(\'node-approved.txt\',cp.execSync(\'echo node-approved\',{encoding:\'utf8\'}).trim())"' : 'echo approved>approved.txt'
|
|
response.write(frame(body, { role: 'assistant', tool_calls: [{ index: 0, id: `sandbox-tool-${requests.length}`, type: 'function', function: { name: hostGitArgs ? 'sandbox_git' : workspaceAction ? 'sandbox_workspace' : 'execute_command', arguments: JSON.stringify(hostGitArgs ?? (workspaceAction ? { action: workspaceAction } : { command,
|
|
...(prompt.includes('DRAFT') ? { workspace: 'persistent', runtimes: ['git'] } : prompt.includes('GIT') ? { runtimes: ['git'] } : prompt.includes('READ_BRIDGE') ? { runtimes: ['node'], bridge: 'workspace-read' } : prompt.includes('NODE') ? { runtimes: ['node'] } : {}) })) } }] }))
|
|
response.write(frame(body, {}, 'tool_calls'))
|
|
} else { response.write(frame(body, { role: 'assistant', content: 'SANDBOX_TOOL_RESULT_RECORDED' })); response.write(frame(body, {}, 'stop')) }
|
|
response.end('data: [DONE]\n\n')
|
|
})
|
|
await new Promise(done => server.listen(0, '127.0.0.1', done))
|
|
await import(pathToFileURL(path.resolve('out/main/index.mjs')).href)
|
|
await app.whenReady()
|
|
await wait(async () => {
|
|
win = BrowserWindow.getAllWindows().find(window => window.getTitle() === 'YCode')
|
|
return win && !win.webContents.isLoading() && await read('!!window.ycode?.app?.sandboxStatus && !!document.querySelector(".chat-pane")')
|
|
}, 'production preload and renderer')
|
|
const status = await invoke('app', 'sandboxStatus')
|
|
check('production main/preload IPC exposes installed native backend', status.state === 'installed' && status.network === 'deny')
|
|
const bundledGit = status.toolchains.find(item => item.kind === 'git')
|
|
check('fresh production userData has bundled Git without a user registration', bundledGit.state === 'registered' && bundledGit.source === 'bundled' && !!bundledGit.version && !await fs.stat(path.join(directory, 'data/sandbox/toolchains/git.json')).catch(() => null))
|
|
check('installed status does not invent a native self-test observation', !status.selfTest && status.capabilities.some(item => item.id === 'filesystem-broker' && item.state === 'unavailable'))
|
|
await read('window.sandboxQaPolicyEvents=0; window.ycode.app.onSandboxChanged(()=>window.sandboxQaPolicyEvents++); true')
|
|
const initialPolicy = await invoke('app', 'sandboxPolicy')
|
|
let policy = await invoke('app', 'setSandboxPolicy', { expectedRevision: initialPolicy.revision, rules: { maxChanges: 128 } })
|
|
check('production preload updates a versioned policy and broadcasts changes', policy.revision !== initialPolicy.revision && (await invoke('app', 'sandboxStatus')).policy.revision === policy.revision && await read('window.sandboxQaPolicyEvents>0'))
|
|
check('production IPC rejects stale policy writes', await invoke('app', 'setSandboxPolicy', { expectedRevision: initialPolicy.revision, rules: { commandsEnabled: false } }).then(() => false, () => true))
|
|
check('production IPC rejects unsupported network permissions', await invoke('app', 'setSandboxPolicy', { expectedRevision: policy.revision, rules: { network: 'allow' } }).then(() => false, () => true))
|
|
check('production IPC rejects renderer-supplied workspace policy paths', await invoke('app', 'setSandboxPolicy', { expectedRevision: policy.revision, rules: {}, workspaceRoot: directory }).then(() => false, () => true))
|
|
const selfTest = await invoke('app', 'sandboxSelfTest')
|
|
check('production self-test stores a real binary and policy-bound observation', selfTest.passed && (await invoke('app', 'sandboxStatus')).selfTest.runId === selfTest.runId)
|
|
await read('window.sandboxQaEvents=[]; window.ycode.chat.onEvent(event=>window.sandboxQaEvents.push(event)); true')
|
|
await invoke('workspace', 'attach', path.join(directory, 'workspace'))
|
|
await invoke('models', 'save', { id: 'sandbox-local', name: 'Sandbox local QA', providerId: 'custom', model: 'sandbox-upstream', baseUrl: `http://127.0.0.1:${server.address().port}/v1`, authMode: 'none', supportsToolCall: true, maxToolRounds: 4 })
|
|
await invoke('models', 'select', 'sandbox-local')
|
|
async function start(text) {
|
|
const session = await invoke('session', 'create', text)
|
|
const sent = await invoke('chat', 'send', { sessionId: session.id, text, permissionMode: 'default-sandbox' })
|
|
return { session, sent }
|
|
}
|
|
async function approval(runId) {
|
|
return wait(() => read(`window.sandboxQaEvents.find(event=>event.runId===${JSON.stringify(runId)} && event.type==='interaction' && event.interaction.status==='pending')?.interaction`), 'real pending approval')
|
|
}
|
|
async function finished(sessionId) {
|
|
return wait(async () => {
|
|
const messages = await invoke('session', 'messages', sessionId)
|
|
const assistant = messages.filter(message => message.role === 'assistant').at(-1)
|
|
return assistant && assistant.status !== 'streaming' && !await invoke('chat', 'activeRun', sessionId) ? assistant : null
|
|
}, 'persisted assistant terminal')
|
|
}
|
|
let current = await start('SANDBOX_ALLOW')
|
|
let pending = await approval(current.sent.runId)
|
|
check('real OMP host tool reaches ordinary conversation approval', pending.toolCallId && pending.kind === 'approval')
|
|
check('host file is unchanged while writeback awaits approval', !(await fs.stat(path.join(directory, 'workspace/approved.txt')).catch(() => null)))
|
|
const accepted = await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' })
|
|
check('actual chat response IPC accepts the matching run and interaction', accepted.accepted)
|
|
let assistant = await finished(current.session.id)
|
|
const tool = assistant.tools.find(tool => tool.name === 'execute_command')
|
|
check('native sandbox metadata survives OMP result and session persistence', tool.result.sandbox === 'windows-appcontainer' && tool.result.writeback === 'committed' && !!tool.result.transactionId)
|
|
check('production sandbox result retains policy and authorization identity', tool.result.policyRevision === policy.revision && !!tool.result.authorizationId)
|
|
const storedOperation = await invoke('app', 'sandboxOperation', tool.result.runId)
|
|
check('production IPC queries durable host outcome with session ownership', storedOperation.writeback === 'committed' && storedOperation.transactionId === tool.result.transactionId && storedOperation.identity.sessionId === current.session.id)
|
|
check('approved production tool writes actual workspace file', (await fs.readFile(path.join(directory, 'workspace/approved.txt'), 'utf8')).trim() === 'approved')
|
|
current = await start('SANDBOX_DENY'); pending = await approval(current.sent.runId)
|
|
await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'reject-once' })
|
|
assistant = await finished(current.session.id)
|
|
check('rejected production writeback persists rejection and no host file', assistant.tools.find(tool => tool.name === 'execute_command').result.writeback === 'rejected' && !(await fs.stat(path.join(directory, 'workspace/denied.txt')).catch(() => null)))
|
|
current = await start('SANDBOX_NODE'); pending = await approval(current.sent.runId)
|
|
check('real OMP forwards the requested controlled Node runtime into native approval', pending.title.includes('node-approved.txt') && !await fs.stat(path.join(directory, 'workspace/node-approved.txt')).catch(() => null))
|
|
await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' })
|
|
assistant = await finished(current.session.id)
|
|
const nodeTool = assistant.tools.find(tool => tool.name === 'execute_command')
|
|
check('production Node execution commits only after approval and persists runtime identity', nodeTool.result.writeback === 'committed' && nodeTool.result.toolchains[0].kind === 'node' && await fs.readFile(path.join(directory, 'workspace/node-approved.txt'), 'utf8') === 'node-approved')
|
|
const readRequestId = require('node:crypto').randomUUID()
|
|
await fs.writeFile(path.join(directory, 'workspace/read-bridge.cjs'), `const assert=require('assert');const c=require(process.env.YCODE_SANDBOX_BRIDGE).connectSync();try{assert.equal(c.readFileSync('node-approved.txt','utf8'),'node-approved');c.call('workspace.stat',{path:'node-approved.txt'},'${readRequestId}');assert.equal(c.querySync('${readRequestId}').state,'succeeded');console.log('PRODUCTION_READ_BRIDGE_PASS')}finally{c.close()}`)
|
|
current = await start('SANDBOX_READ_BRIDGE'); assistant = await finished(current.session.id)
|
|
const readTool = assistant.tools.find(tool => tool.name === 'execute_command')
|
|
check('real production OMP command reads through the native workspace bridge', readTool.result.workspaceReadBridge === true && readTool.result.stdout.includes('PRODUCTION_READ_BRIDGE_PASS') && readTool.result.writeback === 'unchanged')
|
|
const readOperation = await invoke('app', 'sandboxBrokerOperation', readTool.result.runId, readRequestId)
|
|
check('production preload queries a completed bridge result with session ownership', readOperation.state === 'succeeded' && readOperation.owner.sessionId === current.session.id && readOperation.owner.sandboxRunId === readTool.result.runId)
|
|
check('production bridge query rejects cross-run request substitution', await invoke('app', 'sandboxBrokerOperation', nodeTool.result.runId, readRequestId).then(() => false, () => true))
|
|
current = await start('SANDBOX_GIT'); pending = await approval(current.sent.runId)
|
|
check('real OMP Git execution asks approval for both private repository and file changes', pending.title.includes('Git') && !await fs.stat(path.join(directory, 'workspace/git-approved.txt')).catch(() => null))
|
|
await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' })
|
|
assistant = await finished(current.session.id)
|
|
const gitTool = assistant.tools.find(tool => tool.name === 'execute_command')
|
|
check('production OMP persists bundled Git identity and approved private commit', gitTool.result.toolchains[0].kind === 'git' && gitTool.result.toolchains[0].id === bundledGit.id && gitTool.result.stdout.includes(`git version ${bundledGit.version}`) && gitTool.result.git.mode === 'private-snapshot' && gitTool.result.git.persistence === 'saved' && gitTool.result.writeback === 'committed')
|
|
check('production run-query IPC preserves Git persistence separately from file transaction', (await invoke('app', 'sandboxOperation', gitTool.result.runId)).git.revision === gitTool.result.git.revision)
|
|
current = await start('SANDBOX_GIT_READ'); assistant = await finished(current.session.id)
|
|
const gitRead = assistant.tools.find(tool => tool.name === 'execute_command')
|
|
check('later production OMP command reads the saved commit without another approval', gitRead.result.stdout.includes('production-git-commit') && gitRead.result.git.persistence === 'unchanged' && gitRead.result.writeback === 'unchanged')
|
|
current = await start('SANDBOX_DRAFT_CREATE'); assistant = await finished(current.session.id)
|
|
const draft = assistant.tools.find(tool => tool.name === 'execute_command')
|
|
check('real OMP saves persistent filesystem and Git drafts without host approval', draft.result.writeback === 'deferred' && draft.result.workspace.mode === 'persistent' && draft.result.git.scope === 'draft' && !await fs.stat(path.join(directory, 'workspace/draft-only.txt')).catch(() => null))
|
|
check('deferred draft does not create a hidden conversation approval', !await read(`window.sandboxQaEvents.some(event=>event.runId===${JSON.stringify(current.sent.runId)} && event.type==='interaction' && event.interaction.kind==='approval')`))
|
|
const draftView = await invoke('app', 'sandboxWorkspace', current.session.id)
|
|
check('production preload resolves persistent state from its saved session owner', draftView.state === 'ready' && draftView.revision === draft.result.workspace.revision && draftView.changes.some(change => change.path === 'draft-only.txt'))
|
|
check('production run query preserves deferred outcome and draft ownership', (await invoke('app', 'sandboxOperation', draft.result.runId)).workspace.sessionId === current.session.id)
|
|
check('workspace IPC rejects renderer paths and missing saved sessions', await invoke('app', 'sandboxWorkspace', { sessionId: current.session.id, workspaceRoot: directory }).then(() => false, () => true)
|
|
&& await invoke('app', 'sandboxWorkspace', require('node:crypto').randomUUID()).then(() => false, () => true))
|
|
const unrelated = await invoke('session', 'create', 'unrelated-draft-owner')
|
|
check('another saved session cannot query the first session draft by changing paths', (await invoke('app', 'sandboxWorkspace', unrelated.id)).state === 'missing')
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_DRAFT_READ', permissionMode: 'default-sandbox' })
|
|
assistant = await finished(current.session.id)
|
|
const draftRead = assistant.tools.find(tool => tool.name === 'execute_command')
|
|
check('later OMP turn reads retained physical files and draft Git history', draftRead.result.stdout.includes('draft-only') && draftRead.result.stdout.includes('production-draft-commit') && draftRead.result.writeback === 'deferred')
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_DRAFT_COMMIT', permissionMode: 'default-sandbox' })
|
|
pending = await approval(current.sent.runId)
|
|
check('workspace commit reaches the real approval UI while status remains responsive', pending.title.includes('draft-only.txt') && (await invoke('app', 'sandboxWorkspace', current.session.id)).state === 'busy' && !await fs.stat(path.join(directory, 'workspace/draft-only.txt')).catch(() => null))
|
|
await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' })
|
|
assistant = await finished(current.session.id)
|
|
const draftCommit = assistant.tools.find(tool => tool.name === 'sandbox_workspace')
|
|
check('new workspace tool commits approved bytes through production OMP routing', draftCommit.result.writeback === 'committed' && !!draftCommit.result.transactionId && (await fs.readFile(path.join(directory, 'workspace/draft-only.txt'), 'utf8')).trim() === 'draft-only')
|
|
check('committed workspace status clears pending changes and retains native transaction ID', (await invoke('app', 'sandboxWorkspace', current.session.id)).lastTransactionId === draftCommit.result.transactionId && draftCommit.result.changes.length === 0)
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_DRAFT_EXTRA', permissionMode: 'default-sandbox' })
|
|
await finished(current.session.id)
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_DRAFT_DISCARD', permissionMode: 'default-sandbox' })
|
|
assistant = await finished(current.session.id)
|
|
check('production discard archives private changes without mutating host files', assistant.tools.find(tool => tool.name === 'sandbox_workspace').result.changes.length === 0 && !await fs.stat(path.join(directory, 'workspace/draft-extra.txt')).catch(() => null) && await fs.stat(path.join(directory, 'workspace/draft-only.txt')))
|
|
fixtureGit('init', '-q', '--template=', '--initial-branch=main'); fixtureGit('add', '.'); fixtureGit('commit', '-q', '-m', 'production-real-host-history')
|
|
fixtureGit('config', 'credential.helper', '!echo HOST_HELPER_MUST_NOT_RUN')
|
|
current = await start('SANDBOX_HOST_GIT_IMPORT'); pending = await approval(current.sent.runId)
|
|
check('real OMP routes host-history import through an explicit scope approval', pending.title.includes('真实 Git 历史') && (await invoke('app', 'sandboxGit', current.session.id)).state === 'unimported')
|
|
await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' })
|
|
assistant = await finished(current.session.id)
|
|
check('production Git tool and preload share the saved session mirror identity', assistant.tools.find(tool => tool.name === 'sandbox_git').result.revision === (await invoke('app', 'sandboxGit', current.session.id)).revision)
|
|
check('Git status IPC rejects arbitrary renderer roots and unknown saved sessions', await invoke('app', 'sandboxGit', { sessionId: current.session.id, workspaceRoot: directory }).then(() => false, () => true)
|
|
&& await invoke('app', 'sandboxGit', require('node:crypto').randomUUID()).then(() => false, () => true))
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_HOST_GIT_READ', permissionMode: 'default-sandbox' })
|
|
assistant = await finished(current.session.id)
|
|
check('production native argv Git command sees the real host history', assistant.tools.find(tool => tool.name === 'sandbox_git').result.stdout.includes('production-real-host-history'))
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_HOST_GIT_CHANGE', permissionMode: 'default-sandbox' })
|
|
assistant = await finished(current.session.id)
|
|
check('production local Git commit stays private until publication', assistant.tools.find(tool => tool.name === 'sandbox_git').result.git.mode === 'host-mirror' && !fixtureGit('log', '-1', '--oneline').toString().includes('production-host-git-commit'))
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_HOST_GIT_COMMIT', permissionMode: 'default-sandbox' }); pending = await approval(current.sent.runId)
|
|
check('production combined Git publication is approval-bound and reports busy without waiting', pending.title.includes('真实 Git 元数据') && (await invoke('app', 'sandboxGit', current.session.id)).state === 'busy')
|
|
await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' }); assistant = await finished(current.session.id)
|
|
check('real OMP publishes the exact local commit into the actual host repository', assistant.tools.find(tool => tool.name === 'sandbox_git').result.writeback === 'committed' && fixtureGit('log', '-1', '--oneline').toString().includes('production-host-git-commit') && !(await invoke('app', 'sandboxGit', current.session.id)).pendingMetadata)
|
|
current.sent = await invoke('chat', 'send', { sessionId: current.session.id, text: 'SANDBOX_HOST_GIT_REMOTE', permissionMode: 'default-sandbox' }); pending = await approval(current.sent.runId)
|
|
check('production remote transport names its concrete endpoint in the approval', pending.title.includes('/git/repo.git'))
|
|
await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' }); assistant = await finished(current.session.id)
|
|
const remoteGit = assistant.tools.find(tool => tool.name === 'sandbox_git').result
|
|
check('real OMP approved smart HTTP discovery persists an owned durable result', remoteGit.state === 'completed' && remoteGit.stdout.includes('refs/heads/main') && (await invoke('app', 'sandboxGitRemoteOperation', current.session.id, remoteGit.operationId)).state === 'completed')
|
|
check('another session cannot query an existing remote operation ID', await invoke('app', 'sandboxGitRemoteOperation', unrelated.id, remoteGit.operationId).then(() => false, () => true))
|
|
current = await start('SANDBOX_REVOKE'); pending = await approval(current.sent.runId)
|
|
policy = await invoke('app', 'setSandboxPolicy', { expectedRevision: policy.revision, rules: { maxChanges: 127 } })
|
|
assistant = await finished(current.session.id)
|
|
const cancelledApproval = await read(`window.sandboxQaEvents.some(event=>event.runId===${JSON.stringify(current.sent.runId)} && event.type==='interaction' && event.interaction.id===${JSON.stringify(pending.id)} && event.interaction.status==='cancelled')`)
|
|
check('production policy revocation cancels the real pending approval and refuses host writeback', cancelledApproval && assistant.tools.find(tool => tool.name === 'execute_command').status === 'error' && !await fs.stat(path.join(directory, 'workspace/revoked.txt')).catch(() => null))
|
|
check('revoked production approval rejects a late button response', (await invoke('chat', 'respond', current.sent.runId, pending.id, { kind: 'approval', optionId: 'allow-once' })).accepted === false)
|
|
check('production policy change invalidates the previous self-test identity', !(await invoke('app', 'sandboxStatus')).selfTest)
|
|
current = await start('SANDBOX_CANCEL')
|
|
const runsRoot = path.join(directory, 'data/sandbox/runs')
|
|
const nativeRun = await wait(async () => {
|
|
for (const entry of await fs.readdir(runsRoot)) {
|
|
const request = await fs.readFile(path.join(runsRoot, entry, 'request.json'), 'utf8').then(raw => JSON.parse(raw), error => { if (error.code === 'ENOENT') return null; throw error })
|
|
if (request?.identity.runId === current.sent.runId && await fs.stat(path.join(runsRoot, entry, 'lease.json')).catch(() => null)) return entry
|
|
}
|
|
return null
|
|
}, 'cancel scenario native startup')
|
|
await invoke('chat', 'cancel', current.sent.runId)
|
|
assistant = await finished(current.session.id)
|
|
await wait(() => fs.stat(path.join(runsRoot, nativeRun, 'cleanup.json')).catch(() => null), 'native cancellation cleanup')
|
|
const report = JSON.parse(await fs.readFile(path.join(runsRoot, nativeRun, 'result.json'), 'utf8'))
|
|
check('production cancellation reaches native Job, terminal persistence and cleanup', assistant.status === 'cancelled' && report.status === 'cancelled' && report.profileRemoved && report.driveRemoved)
|
|
check('cancelled command cannot write the later file', !(await fs.stat(path.join(directory, 'workspace/late.txt')).catch(() => null)))
|
|
check('all model traffic used the explicitly local test server', requests.length >= 5 && requests.every(request => request.model === 'sandbox-upstream'))
|
|
await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ checks, requests, scope: 'Actual production renderer/preload/main, real OMP RPC and native sandbox; local-only deterministic model responses. No original WorkBuddy runtime was used.' }, null, 2) + '\n')
|
|
console.log(`SANDBOX_APP_PASS ${checks.length}/${checks.length}`)
|
|
await new Promise(done => server.close(done))
|
|
app.quit()
|
|
})().catch(async error => {
|
|
console.error(error)
|
|
await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ checks, error: error.stack }, null, 2)).catch(() => undefined)
|
|
if (server) server.close()
|
|
app.exit(1)
|
|
})
|
|
}
|