mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
* Chat UI leaves Experimental: each agent's Chat | CLI choice decides launches Remove the experimentalNativeChat switch and every gate on it. The Chat settings page, Default Agent Chat | CLI choice, + menu Chat/CLI rows, source-control surface choice, Antigravity chat section and orchestration chat-error switch are always present. What a new launch opens comes only from the per-agent choice (no saved choice opens a chat) plus the existing support checks. - One lookup, nativeChatPreferenceFor, keyed by agent id so host-registered agents read the same way; unreadable settings still open a terminal. - A relayed desktop stamp (nativeChatDefault) maps onto the launched agent; an explicit surface still wins. Old phones' createSupport keeps admitting by the host's per-agent choice. - settings.get stops publishing the retired switch keys; nothing in the repo reads them from a host. - Startup chat sync and hydration run when some agent can open as a chat or the machine already holds chats. - The retired key is stripped on load. The one-time upgrade tip from the chat upgrade is retired with its audience record; a new record, captured once from the raw profile, marks which existing profiles had Chat UI off. - The window-side AI-button tab path, which only ran with the switch off, is deleted. * Keep profiles that had Chat UI off on the terminal Existing users who never turned Chat UI on would otherwise see new agent launches open as chats, since an agent without a saved choice now opens as a chat. The one-time load step that records the new audience also sets every known agent to CLI for those profiles, in the same save and only when the record was absent, overwriting saved chat choices the old switch masked. Profiles that had Chat UI on and new profiles are untouched. Browser profiles keep their own settings, so the web client does the same once: a stored switch that is not on seeds CLI choices, and dropping the key marks the step done. Agents first seen after this step open as chats. * Agents without a saved Chat | CLI choice open as their CLI An agent with no saved choice now opens in the terminal for everyone, including fresh installs and agents first seen later. The one-time load step carries what launches opened while Chat UI was experimental: with the switch on, every known agent without a saved choice is saved as chat; with it off, chat choices the switch masked are saved as CLI. Existing profiles join the launch tip audience whether the switch was on or off. Browser profiles get the same carry from their saved key. The startup chat restore and host agent list now run only when some agent is saved as chat or the runtime holds chats. * Preserve owned terminal launches and local chat choices * Clarify chat and terminal support * Explain worker CLI integration timeout budget * Use checked annotations for chat graduation test fixtures * Store agent surface default separately from per-agent overrides * Preserve explicit Chat intent across default changes * Preserve current browser settings during host replies * Correct browser settings factory and regression types * Name exported launch fixture mock types and clarify agent defaults * Preserve settings fallbacks and complete launch test lifecycles * Check allocated tab identity before launch fixture interpolation * Register combined integration project for compiler parity probes * Use checked settings in the launch test fixture * Preserve retired Chat preference data privately * Align chat E2E fixtures and capture pane lifecycle evidence * Use typed raw settings in retention contracts * Consolidate transcript diagnostic imports * test: align chat fixtures with live pane and native menu contracts * test: capture relay tab selection loss diagnostics * test: wait for owned shells before synthetic chat fixtures * test: preserve all-host visibility in SSH observer * test: bind synthetic transcript fixtures before provider seeding * test: assert selected chat owner after narrow image preview * ci: allow fifth compiler project to complete comparison * Type the web-terminal fixture's chat map without an assertion * Right-click the selection itself in the chat copy spec
269 lines
9.5 KiB
JavaScript
269 lines
9.5 KiB
JavaScript
import { readPinnedBunVersion } from './pinned-bun-executable.mjs'
|
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { availableParallelism, totalmem } from 'node:os'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { performance } from 'node:perf_hooks'
|
|
import { measurePreflightCommand } from './preflight-benchmark-command.mjs'
|
|
import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs'
|
|
import { summarizeBenchmarkSamples } from './benchmark-sample-summary.mjs'
|
|
import {
|
|
TYPECHECK_PROJECTS,
|
|
admissibleHeapGib,
|
|
planTypecheckBatches,
|
|
resolveProjectCompiler
|
|
} from './run-typecheck-projects-in-parallel.mjs'
|
|
|
|
const parityOnly = process.argv.slice(2).includes('--parity-only')
|
|
if (process.argv.slice(2).some((argument) => argument !== '--parity-only')) {
|
|
throw new Error('Use --parity-only or no arguments')
|
|
}
|
|
const root = fileURLToPath(new URL('../..', import.meta.url))
|
|
const output = path.join(root, '.build/preflight-benchmark')
|
|
const reportName = parityOnly ? 'compiler-parity' : 'compilers'
|
|
mkdirSync(output, { recursive: true })
|
|
const bun = process.env.ORCA_BENCHMARK_BUN ?? 'bun'
|
|
const env = { ...process.env, ORCA_BACKGROUND_LAUNCH: '1', GOMAXPROCS: '4' }
|
|
const report = {
|
|
sourceSha: process.env.GITHUB_SHA ?? null,
|
|
host: {
|
|
platform: process.platform,
|
|
arch: process.arch,
|
|
node: process.version,
|
|
memoryBytes: totalmem(),
|
|
cores: availableParallelism()
|
|
},
|
|
method: parityOnly
|
|
? 'Diagnostic probes and normalized file-list parity for every registered project; no timing comparison.'
|
|
: 'Four counterbalanced pairs per comparison. Cold removes build state, not filesystem caches. Warm immediately follows its cold run. Same commit and project registry for every arm.',
|
|
measurements: [],
|
|
comparisons: {},
|
|
parity: []
|
|
}
|
|
const save = () =>
|
|
writeFileSync(path.join(output, `${reportName}.json`), `${JSON.stringify(report, null, 2)}\n`)
|
|
const command = (program, args, label) =>
|
|
measurePreflightCommand({
|
|
program,
|
|
args,
|
|
label,
|
|
root,
|
|
output,
|
|
env,
|
|
...(process.platform === 'win32' ? { timeoutMs: 900_000 } : {})
|
|
})
|
|
const version = await command(bun, ['--version'], 'bun-version')
|
|
if (version.code !== 0 || version.stdout.trim() !== readPinnedBunVersion(root)) {
|
|
throw new Error(`Pin Bun ${readPinnedBunVersion(root)} for this comparison`)
|
|
}
|
|
report.host.bun = version.stdout.trim()
|
|
report.host.typescript = JSON.parse(
|
|
readFileSync(path.join(root, 'node_modules/typescript/package.json'))
|
|
).version
|
|
const nativeBatches = planTypecheckBatches(TYPECHECK_PROJECTS, {
|
|
budgetGib: Math.min(12, admissibleHeapGib(totalmem())),
|
|
parallelism: Math.min(4, availableParallelism())
|
|
})
|
|
function invocation(config, arm, extra = []) {
|
|
return arm === 'native'
|
|
? [
|
|
process.execPath,
|
|
[
|
|
resolveProjectCompiler(config, root),
|
|
'--noEmit',
|
|
'-p',
|
|
config,
|
|
'--pretty',
|
|
'false',
|
|
...extra
|
|
]
|
|
]
|
|
: [
|
|
bun,
|
|
[
|
|
'check',
|
|
'-p',
|
|
config,
|
|
'--threads',
|
|
arm === 'bun-pair' ? '2' : '4',
|
|
'--no-pretty',
|
|
'--all',
|
|
...extra
|
|
]
|
|
]
|
|
}
|
|
async function suite(arm, state, pair, comparison) {
|
|
const batches =
|
|
arm === 'native'
|
|
? nativeBatches
|
|
: arm === 'bun-pair'
|
|
? [TYPECHECK_PROJECTS.slice(0, 2), TYPECHECK_PROJECTS.slice(2)]
|
|
: TYPECHECK_PROJECTS.map((project) => [project])
|
|
const start = performance.now()
|
|
const projects = []
|
|
for (const batch of batches) {
|
|
projects.push(
|
|
...(await Promise.all(
|
|
batch.map(async ({ config }) => {
|
|
const [program, args] = invocation(config, arm)
|
|
return {
|
|
config,
|
|
...(await command(
|
|
program,
|
|
args,
|
|
`${comparison}-${pair}-${arm}-${state}-${path.basename(config)}`
|
|
))
|
|
}
|
|
})
|
|
))
|
|
)
|
|
}
|
|
const measurement = {
|
|
comparison,
|
|
pair,
|
|
arm,
|
|
state,
|
|
durationMs: performance.now() - start,
|
|
projects
|
|
}
|
|
report.measurements.push(measurement)
|
|
save()
|
|
console.log(
|
|
JSON.stringify({
|
|
comparison,
|
|
pair,
|
|
arm,
|
|
state,
|
|
durationMs: measurement.durationMs,
|
|
codes: projects.map((project) => project.code),
|
|
peakRssKiB: projects.map((project) => project.peakRssKiB)
|
|
})
|
|
)
|
|
if (projects.some((project) => project.code !== 0 || project.timedOut || project.signal)) {
|
|
throw new Error(`Compiler failed: ${JSON.stringify(projects)}`)
|
|
}
|
|
}
|
|
function clearBuildState() {
|
|
for (const { config } of TYPECHECK_PROJECTS) {
|
|
rmSync(path.join(root, config.replace(/\.json$/, '.tsbuildinfo')), { force: true })
|
|
}
|
|
}
|
|
if (!parityOnly) {
|
|
for (const [comparison, first, second] of [
|
|
['native-vs-bun', 'native', 'bun'],
|
|
['bun-serial-vs-pair', 'bun', 'bun-pair']
|
|
]) {
|
|
for (const [pair, arms] of buildCounterbalancedSchedule(4, first, second).entries()) {
|
|
for (const arm of arms) {
|
|
clearBuildState()
|
|
await suite(arm, arm === 'native' ? 'cold' : 'full', pair, comparison)
|
|
if (arm === 'native') {
|
|
await suite(arm, 'warm', pair, comparison)
|
|
}
|
|
}
|
|
}
|
|
report.comparisons[comparison] = {}
|
|
for (const arm of [first, second]) {
|
|
for (const state of arm === 'native' ? ['cold', 'warm'] : ['full']) {
|
|
const samples = report.measurements.filter(
|
|
(sample) =>
|
|
sample.comparison === comparison && sample.arm === arm && sample.state === state
|
|
)
|
|
report.comparisons[comparison][`${arm}-${state}`] = summarizeBenchmarkSamples(
|
|
samples.map((sample) => sample.durationMs)
|
|
)
|
|
}
|
|
}
|
|
save()
|
|
}
|
|
}
|
|
const probeRoots = new Map([
|
|
['config/tsconfig.node.json', 'src/main'],
|
|
['config/tsconfig.tc.web.json', 'src/renderer/src'],
|
|
['config/tsconfig.tc.cli.json', 'src/cli'],
|
|
['config/tsconfig.test.pane-launch.json', 'src/shared'],
|
|
['src/packages/process-host/tsconfig.test.json', 'src/packages/process-host/src']
|
|
])
|
|
for (const { config } of TYPECHECK_PROJECTS) {
|
|
const probeRoot = probeRoots.get(config)
|
|
if (!probeRoot) {
|
|
throw new Error(`Register an included probe directory for ${config}`)
|
|
}
|
|
const directory = mkdtempSync(path.join(root, probeRoot, 'preflight-benchmark-probe-'))
|
|
const probe = path.join(directory, 'preflight-typecheck-benchmark-probe.ts')
|
|
try {
|
|
writeFileSync(
|
|
probe,
|
|
[
|
|
'export const preflightBenchmarkValue: number = "typecheck must reject this"',
|
|
'export function preflightBenchmarkReturn(value: number): string { return value }',
|
|
'export const preflightBenchmarkNull: string = null',
|
|
'export function preflightBenchmarkImplicit(value) { return value }'
|
|
].join('\n'),
|
|
{ flag: 'wx' }
|
|
)
|
|
for (const arm of ['native', 'bun']) {
|
|
const [program, args] = invocation(config, arm)
|
|
const result = await command(program, args, `probe-${path.basename(config)}-${arm}`)
|
|
report.parity.push({ config, arm, ...result })
|
|
save()
|
|
if (
|
|
result.code !== 1 ||
|
|
(`${result.stdout}${result.stderr}`.match(/: error TS2322:/g) ?? []).length !== 3
|
|
) {
|
|
throw new Error(`Missing expected diagnostic: ${arm} ${config}`)
|
|
}
|
|
}
|
|
const errors = report.parity
|
|
.filter((sample) => sample.config === config && sample.code === 1)
|
|
.map((sample) =>
|
|
`${sample.stdout}${sample.stderr}`
|
|
.split(/\r?\n/)
|
|
.filter((line) => line.includes(': error TS'))
|
|
)
|
|
if (JSON.stringify(errors[0]) !== JSON.stringify(errors[1])) {
|
|
throw new Error(`Compiler diagnostics differ: ${config}`)
|
|
}
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true })
|
|
}
|
|
for (const arm of ['native', 'bun']) {
|
|
const [program, args] = invocation(config, arm, ['--listFilesOnly'])
|
|
const result = await command(program, args, `files-${path.basename(config)}-${arm}`)
|
|
if (result.code !== 0) {
|
|
throw new Error(`Could not enumerate ${arm} ${config}: ${result.stderr}`)
|
|
}
|
|
report.parity.push({
|
|
config,
|
|
arm,
|
|
files: result.stdout
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.filter((line) => path.isAbsolute(line) || line.startsWith('bundled:///libs/'))
|
|
// Bun embeds the standard library; compare its names with native TypeScript's disk paths.
|
|
.map((line) =>
|
|
line.startsWith('bundled:///libs/') ||
|
|
/\/(?:@typescript\/typescript-[^/]+|typescript)\/lib\/lib\.[^/]+\.d\.ts$/.test(
|
|
line.replaceAll('\\', '/')
|
|
)
|
|
? `typescript-lib:${path.basename(line)}`
|
|
: path.normalize(line)
|
|
)
|
|
.sort()
|
|
})
|
|
}
|
|
const [nativeFiles, bunFiles] = report.parity
|
|
.filter((sample) => sample.config === config && sample.files)
|
|
.map((sample) => sample.files)
|
|
save()
|
|
if (JSON.stringify(nativeFiles) !== JSON.stringify(bunFiles)) {
|
|
throw new Error(`Compiler file selection differs: ${config}`)
|
|
}
|
|
save()
|
|
}
|
|
const summary = `Compiler comparison on ${process.platform}/${process.arch}, ${report.host.cores} cores\n\n\`\`\`json\n${JSON.stringify(report.comparisons, null, 2)}\n\`\`\`\n\nAll project file lists match after normalizing embedded standard-library names; both compilers report identical diagnostics for assignment, return-type, strict-null and implicit-any probes under each project's existing settings.\n`
|
|
writeFileSync(path.join(output, `${reportName}.md`), summary)
|
|
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
writeFileSync(process.env.GITHUB_STEP_SUMMARY, summary, { flag: 'a' })
|
|
}
|