mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
test: remove low-value bridge speech and lineage checks (#40356)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -7298,7 +7298,6 @@
|
||||
"src/shared/agent-status/transport/agent-status-osc-pending-retention.test.ts": 154,
|
||||
"src/shared/agent-status/transport/agent-status-osc.test.ts": 104,
|
||||
"src/shared/agent-status/store/agent-status-store-reopen.test.ts": 199,
|
||||
"src/shared/agent-status/contracts/agent-status-subject.test.ts": 171,
|
||||
"src/shared/agent-status/title/agent-status-tracker-program-activity.test.ts": 77,
|
||||
"src/shared/agent-launch/startup/agent-tui-input-clear.test.ts": 57,
|
||||
"src/shared/ai-vault/resume/ai-vault-resume-command.test.ts": 70,
|
||||
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// Measure real cadence picker against independent ±10% jitter over simulated time.
|
||||
// TTL-deduped same-tick work shares one capture, so count distinct wake instants rather than pane wakes.
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import nodeModule from 'node:module'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
|
||||
if (!process.execArgv.includes('--experimental-transform-types')) {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
['--experimental-transform-types', '--no-warnings', import.meta.filename],
|
||||
{ stdio: 'inherit' }
|
||||
)
|
||||
process.exit(result.status ?? 1)
|
||||
}
|
||||
|
||||
nodeModule.registerHooks({
|
||||
resolve(specifier, context, nextResolve) {
|
||||
if (specifier.startsWith('.') && !/\.[cm]?[jt]s$/.test(specifier) && context.parentURL) {
|
||||
const candidate = new URL(`${specifier}.ts`, context.parentURL)
|
||||
if (fs.existsSync(fileURLToPath(candidate))) {
|
||||
return { url: candidate.href, shortCircuit: true }
|
||||
}
|
||||
}
|
||||
return nextResolve(specifier, context)
|
||||
}
|
||||
})
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname, '../../../..')
|
||||
const WINDOW_MS = Number(process.env.ORCA_INSPECTION_BENCH_WINDOW_MS ?? '60000')
|
||||
const PANE_COUNTS = (process.env.ORCA_INSPECTION_BENCH_PANES ?? '1,2,4,8')
|
||||
.split(',')
|
||||
.map((value) => Number(value.trim()))
|
||||
|
||||
if (!Number.isSafeInteger(WINDOW_MS) || WINDOW_MS <= 0) {
|
||||
throw new Error(`ORCA_INSPECTION_BENCH_WINDOW_MS must be a positive integer, got ${WINDOW_MS}`)
|
||||
}
|
||||
for (const paneCount of PANE_COUNTS) {
|
||||
if (!Number.isSafeInteger(paneCount) || paneCount <= 0) {
|
||||
throw new Error(`ORCA_INSPECTION_BENCH_PANES entries must be positive, got ${paneCount}`)
|
||||
}
|
||||
}
|
||||
|
||||
const { nextCadenceInspectionDelayMs } = await import(
|
||||
pathToFileURL(
|
||||
path.join(
|
||||
ROOT,
|
||||
'src/renderer/src/components/terminal-pane/agent-completion/agent-completion-poll-interval.ts'
|
||||
)
|
||||
).href
|
||||
)
|
||||
const { POLL_TIER_INTERVAL_MS } = await import(
|
||||
pathToFileURL(
|
||||
path.join(
|
||||
ROOT,
|
||||
'src/renderer/src/components/terminal-pane/agent-completion/agent-completion-poll-cadence.ts'
|
||||
)
|
||||
).href
|
||||
)
|
||||
const { PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS } = await import(
|
||||
pathToFileURL(path.join(ROOT, 'src/shared/process-table-snapshot-reader.ts')).href
|
||||
)
|
||||
|
||||
// Pre-change: independent ±10% jitter per pane, re-rolled on every reschedule.
|
||||
function baselineDelayMs(baseMs) {
|
||||
return Math.round(baseMs * (1 + (Math.random() * 0.2 - 0.1)))
|
||||
}
|
||||
|
||||
function simulate(paneCount, baseMs, pickDelay) {
|
||||
const startedAt = 1_700_000_000_000
|
||||
const wakes = []
|
||||
for (let pane = 0; pane < paneCount; pane += 1) {
|
||||
// Panes mount at arbitrary moments, which is what spreads them apart in the first place.
|
||||
let clock = startedAt + Math.floor(Math.random() * baseMs)
|
||||
while ((clock += pickDelay(baseMs, clock)) < startedAt + WINDOW_MS) {
|
||||
wakes.push(clock)
|
||||
}
|
||||
}
|
||||
// Snapshot TTL starts at actual capture, not epoch grid; nearby wakes reuse it.
|
||||
let captures = 0
|
||||
let snapshotExpiresAt = -Infinity
|
||||
for (const wakeAt of wakes.sort((left, right) => left - right)) {
|
||||
if (wakeAt >= snapshotExpiresAt) {
|
||||
captures += 1
|
||||
snapshotExpiresAt = wakeAt + PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS
|
||||
}
|
||||
}
|
||||
return captures
|
||||
}
|
||||
|
||||
function medianOf(rounds, run) {
|
||||
const samples = Array.from({ length: rounds }, run).sort((left, right) => left - right)
|
||||
return samples[Math.floor(samples.length / 2)]
|
||||
}
|
||||
|
||||
const baseMs = POLL_TIER_INTERVAL_MS.idle
|
||||
console.log(
|
||||
`Agent-completion cadence — whole-host \`ps\` captures over ${WINDOW_MS / 1000}s at the idle tier (${baseMs}ms)\n`
|
||||
)
|
||||
console.log('| visible panes | before | after | reduction |')
|
||||
console.log('| --- | --- | --- | --- |')
|
||||
for (const paneCount of PANE_COUNTS) {
|
||||
const before = medianOf(21, () => simulate(paneCount, baseMs, baselineDelayMs))
|
||||
const after = medianOf(21, () =>
|
||||
simulate(paneCount, baseMs, (base, now) =>
|
||||
nextCadenceInspectionDelayMs({
|
||||
baseMs: base,
|
||||
hasConsecutiveErrors: false,
|
||||
alignToSharedGrid: true,
|
||||
now
|
||||
})
|
||||
)
|
||||
)
|
||||
// Short windows may yield no baseline captures; avoid meaningless division by zero.
|
||||
const reduction = before > 0 ? `${(((before - after) / before) * 100).toFixed(0)}%` : 'n/a'
|
||||
console.log(`| ${paneCount} | ${before} | ${after} | ${reduction} |`)
|
||||
}
|
||||
|
||||
// Detection latency must not regress: the grid deadline is always within one interval.
|
||||
let worstDelay = 0
|
||||
for (let sample = 0; sample < 100_000; sample += 1) {
|
||||
const now = 1_700_000_000_000 + sample * 7
|
||||
worstDelay = Math.max(
|
||||
worstDelay,
|
||||
nextCadenceInspectionDelayMs({
|
||||
baseMs,
|
||||
hasConsecutiveErrors: false,
|
||||
alignToSharedGrid: true,
|
||||
now
|
||||
})
|
||||
)
|
||||
}
|
||||
if (worstDelay > baseMs) {
|
||||
throw new Error(`grid alignment delayed a poll to ${worstDelay}ms, above the ${baseMs}ms tier`)
|
||||
}
|
||||
console.log(
|
||||
`\nWorst observed wait: ${worstDelay}ms (tier interval ${baseMs}ms) — no inspection is ever delayed.`
|
||||
)
|
||||
@@ -1,90 +0,0 @@
|
||||
// Run: node --expose-gc config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs [baseline-ref]
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { transform } from 'esbuild'
|
||||
import { buildCounterbalancedSchedule } from '../measurement/counterbalanced-benchmark-schedule.mjs'
|
||||
|
||||
const sourcePath = 'src/renderer/src/components/dashboard/agent-row-lineage-model.ts'
|
||||
const baseline = process.argv[2] ?? '20ab9950654'
|
||||
const beforeSource = execFileSync('git', ['show', `${baseline}:${sourcePath}`], {
|
||||
encoding: 'utf8',
|
||||
windowsHide: true
|
||||
})
|
||||
async function load(source) {
|
||||
const { code } = await transform(source, { loader: 'ts', format: 'esm' })
|
||||
return (await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`))
|
||||
.buildAgentRowLineageTree
|
||||
}
|
||||
const before = await load(beforeSource)
|
||||
const after = await load(await readFile(sourcePath, 'utf8'))
|
||||
|
||||
function row(index, parent) {
|
||||
return {
|
||||
paneKey: `pane-${index}`,
|
||||
entry: {
|
||||
terminalHandle: `term-${index}`,
|
||||
orchestration: parent === undefined ? undefined : { parentPaneKey: `pane-${parent}` }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exercise duplicate keys, disconnected cycles, missing parents, and handle fallback.
|
||||
let seed = 7391
|
||||
function random(max) {
|
||||
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0
|
||||
return seed % max
|
||||
}
|
||||
for (let sample = 0; sample < 500; sample++) {
|
||||
const rows = Array.from({ length: 40 }, () => {
|
||||
const value = row(random(30), random(40))
|
||||
value.entry.orchestration.parentTerminalHandle = `term-${random(40)}`
|
||||
value.entry.orchestration.coordinatorHandle = `term-${random(40)}`
|
||||
return value
|
||||
})
|
||||
if (sample % 2 === 0) {
|
||||
rows.unshift(row('root', undefined))
|
||||
}
|
||||
assert.deepEqual(after(rows), before(rows))
|
||||
}
|
||||
|
||||
const results = []
|
||||
for (const [topology, count] of [
|
||||
['flat', 1000],
|
||||
['all-cycles', 1000],
|
||||
['mixed-cycles', 100],
|
||||
['mixed-cycles', 500],
|
||||
['mixed-cycles', 1000]
|
||||
]) {
|
||||
const rows = Array.from({ length: count }, (_, index) =>
|
||||
row(index, topology === 'flat' ? undefined : index ^ 1)
|
||||
)
|
||||
if (topology === 'mixed-cycles') {
|
||||
rows.unshift(row('root', undefined))
|
||||
}
|
||||
assert.deepEqual(after(rows), before(rows))
|
||||
for (let warmup = 0; warmup < 30; warmup++) {
|
||||
before(rows)
|
||||
after(rows)
|
||||
}
|
||||
const samples = { before: [], after: [] }
|
||||
for (const pair of buildCounterbalancedSchedule(8, 'before', 'after')) {
|
||||
for (const arm of pair) {
|
||||
global.gc?.()
|
||||
const run = arm === 'before' ? before : after
|
||||
const cpu = process.cpuUsage()
|
||||
const start = performance.now()
|
||||
for (let iteration = 0; iteration < 30; iteration++) {
|
||||
run(rows)
|
||||
}
|
||||
const wallMs = (performance.now() - start) / 30
|
||||
const used = process.cpuUsage(cpu)
|
||||
samples[arm].push({ wallMs, cpuMs: (used.user + used.system) / 30_000 })
|
||||
}
|
||||
}
|
||||
results.push({ topology, count, samples })
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify({ baseline, node: process.version, parityGraphs: 500, results }, null, 2)
|
||||
)
|
||||
@@ -1,118 +0,0 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { transform } from 'esbuild'
|
||||
import { buildCounterbalancedSchedule } from '../measurement/counterbalanced-benchmark-schedule.mjs'
|
||||
import { summarizeBenchmarkSamples } from '../measurement/benchmark-sample-summary.mjs'
|
||||
|
||||
// git show <ref>:src/renderer/src/components/dashboard/agent-row-lineage-model.ts | node config/scripts/agent-lineage-reachability-benchmark.mjs
|
||||
async function load(source) {
|
||||
const { code } = await transform(source, { loader: 'ts', format: 'esm' })
|
||||
return (await import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`))
|
||||
.buildAgentRowLineageTree
|
||||
}
|
||||
const implementations = {
|
||||
before: await load(readFileSync(0, 'utf8')),
|
||||
after: await load(
|
||||
readFileSync('src/renderer/src/components/dashboard/agent-row-lineage-model.ts', 'utf8')
|
||||
)
|
||||
}
|
||||
function orderedTree(tree) {
|
||||
return {
|
||||
roots: tree.rootRows,
|
||||
children: [...tree.childrenByParentPaneKey],
|
||||
childKeys: [...tree.childPaneKeys]
|
||||
}
|
||||
}
|
||||
|
||||
let seed = 42
|
||||
const random = (max) => {
|
||||
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0
|
||||
return Math.floor((seed / 2 ** 32) * max)
|
||||
}
|
||||
let differentialCases = 0
|
||||
for (let trial = 0; trial < 5000; trial += 1) {
|
||||
const count = random(100)
|
||||
const rows = Object.freeze(
|
||||
Array.from({ length: count }, (_, index) =>
|
||||
Object.freeze({
|
||||
paneKey: `pane-${random(count + 4)}`,
|
||||
index,
|
||||
entry: Object.freeze({
|
||||
terminalHandle: random(2) ? `term-${random(count)}` : undefined,
|
||||
orchestration: Object.freeze({
|
||||
parentPaneKey: random(3) ? `pane-${random(count + 4)}` : undefined,
|
||||
parentTerminalHandle: random(2) ? `term-${random(count)}` : undefined,
|
||||
coordinatorHandle: random(2) ? `term-${random(count)}` : undefined
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
assert.deepEqual(
|
||||
orderedTree(implementations.after(rows)),
|
||||
orderedTree(implementations.before(rows))
|
||||
)
|
||||
differentialCases += 1
|
||||
}
|
||||
|
||||
const results = []
|
||||
for (const count of [8, 32, 128, 512, 1024]) {
|
||||
for (const topology of ['flat', 'fanout', 'balanced', 'chain']) {
|
||||
const rows = Array.from({ length: count }, (_, index) => {
|
||||
const parent =
|
||||
topology === 'fanout'
|
||||
? 0
|
||||
: topology === 'balanced'
|
||||
? Math.floor((index - 1) / 4)
|
||||
: index - 1
|
||||
return {
|
||||
paneKey: `pane-${index}`,
|
||||
entry: {
|
||||
orchestration:
|
||||
index > 0 && topology !== 'flat' ? { parentPaneKey: `pane-${parent}` } : undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
const expected = orderedTree(implementations.before(rows))
|
||||
assert.deepEqual(orderedTree(implementations.after(rows)), expected)
|
||||
const iterations = Math.max(5, Math.floor(10_000 / count))
|
||||
for (let warmup = 0; warmup < 20; warmup += 1) {
|
||||
implementations.before(rows)
|
||||
implementations.after(rows)
|
||||
}
|
||||
/** @type {{ before: number[], after: number[] }} */
|
||||
const samples = { before: [], after: [] }
|
||||
for (const pair of buildCounterbalancedSchedule(8, 'before', 'after')) {
|
||||
for (const arm of pair) {
|
||||
let result
|
||||
const started = performance.now()
|
||||
for (let repeat = 0; repeat < iterations; repeat += 1) {
|
||||
result = implementations[arm](rows)
|
||||
}
|
||||
samples[arm].push(performance.now() - started)
|
||||
assert.deepEqual(orderedTree(result), expected)
|
||||
}
|
||||
}
|
||||
results.push({
|
||||
count,
|
||||
topology,
|
||||
iterations,
|
||||
meanMicrosecondsPerTree: Object.fromEntries(
|
||||
Object.entries(samples).map(([arm, values]) => [
|
||||
arm,
|
||||
(values.reduce((sum, ms) => sum + ms, 0) * 1000) / values.length / iterations
|
||||
])
|
||||
),
|
||||
before: summarizeBenchmarkSamples(samples.before),
|
||||
after: summarizeBenchmarkSamples(samples.after)
|
||||
})
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{ node: process.version, platform: process.platform, differentialCases, results },
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
@@ -208,7 +208,6 @@
|
||||
"bench:hang-watchdog-memory": "pnpm run ensure:electron-runtime && node config/scripts/diagnostics/hang-watchdog/hang-watchdog-memory-benchmark.mjs",
|
||||
"bench:main-thread-jank": "pnpm run ensure:electron-runtime && node tests/tools/benchmarks/main-thread-jank-bench.mjs",
|
||||
"bench:zustand-selector-fanout": "node config/scripts/performance/renderer-projection/zustand-selector-fanout-benchmark.mjs",
|
||||
"bench:agent-inspection-cadence": "node config/scripts/performance/agent-lineage/agent-inspection-cadence-batching-benchmark.mjs",
|
||||
"bench:renderer-quadratic-scans": "node config/scripts/performance/renderer-markdown/renderer-quadratic-scan-benchmark.mjs",
|
||||
"bench:session-write-hot-path": "node config/scripts/performance/session-transcripts/session-write-hot-path-benchmark.mjs",
|
||||
"bench:terminal-partial-escape-tail": "node config/scripts/performance/terminal-stream/terminal-partial-escape-tail-benchmark.mjs",
|
||||
|
||||
@@ -79,7 +79,8 @@ describe('AgentBrowserBridge keypress input', () => {
|
||||
webContentsFromIdMock.mockImplementation((id: number) => (id === 100 ? wc : null))
|
||||
})
|
||||
|
||||
describe.each(['a', 'MediaPlayPause'])('target admission for %s', (key) => {
|
||||
describe('target admission', () => {
|
||||
const key = 'a'
|
||||
function registerTwoWorkspaces(): MockWebContents {
|
||||
const otherPage = mockWebContents(200)
|
||||
otherPage.debugger.sendCommand.mockResolvedValue({})
|
||||
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
import type * as BrowserErrorTypes from '../../browser-error'
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
const { execFileMock, webContentsFromIdMock, existsSyncMock, readFileSyncMock, stdinWrites } =
|
||||
vi.hoisted(() => ({
|
||||
execFileMock: vi.fn(),
|
||||
webContentsFromIdMock: vi.fn(),
|
||||
existsSyncMock: vi.fn(() => false),
|
||||
readFileSyncMock: vi.fn(() => Buffer.from('')),
|
||||
stdinWrites: new Array<string>()
|
||||
}))
|
||||
|
||||
vi.mock('child_process', () => ({ execFile: execFileMock }))
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: existsSyncMock,
|
||||
readFileSync: readFileSyncMock,
|
||||
accessSync: vi.fn(),
|
||||
chmodSync: vi.fn(),
|
||||
constants: { X_OK: 1 }
|
||||
}))
|
||||
vi.mock('os', () => ({ platform: () => 'darwin', arch: () => 'arm64' }))
|
||||
vi.mock('electron', () => {
|
||||
return {
|
||||
app: { getPath: vi.fn(() => '/app'), getAppPath: vi.fn(() => '/project'), isPackaged: false },
|
||||
webContents: { fromId: webContentsFromIdMock }
|
||||
}
|
||||
})
|
||||
const { CdpWsProxyMock, proxyStops } = vi.hoisted(() => {
|
||||
const instances: unknown[] = []
|
||||
const proxyStops: ReturnType<typeof vi.fn>[] = []
|
||||
const MockClass = vi.fn().mockImplementation(function (
|
||||
this: Record<string, unknown>,
|
||||
_wc: unknown
|
||||
) {
|
||||
this._wc = _wc
|
||||
const endpoint = `ws://127.0.0.1:9222/token-${instances.length}`
|
||||
this.start = vi.fn(async () => endpoint)
|
||||
const stop = vi.fn(async () => {})
|
||||
this.stop = stop
|
||||
proxyStops.push(stop)
|
||||
this.getPort = vi.fn(() => 9222)
|
||||
instances.push(this)
|
||||
})
|
||||
return { CdpWsProxyMock: Object.assign(MockClass, { instances }), proxyStops }
|
||||
})
|
||||
|
||||
vi.mock('../../cdp/transport/cdp-ws-proxy', () => ({
|
||||
CdpWsProxy: CdpWsProxyMock
|
||||
}))
|
||||
vi.mock('../../browser-error', () => ({
|
||||
BrowserError: class BrowserError extends Error {
|
||||
code: string
|
||||
constructor(code: string, message: string) {
|
||||
super(message)
|
||||
this.code = code
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../cdp/commands/cdp-bridge', async () => {
|
||||
const { BrowserError } = await vi.importMock<typeof BrowserErrorTypes>('../../browser-error')
|
||||
return { BrowserError }
|
||||
})
|
||||
|
||||
import { AgentBrowserBridge } from '../agent-browser-bridge'
|
||||
import {
|
||||
mockBrowserManager,
|
||||
overrideBridgeWebContentsLookup,
|
||||
resetAgentBrowserBridgeMocks,
|
||||
succeedRecordingAgentBrowserSpawns
|
||||
} from '../testing/agent-browser-bridge-test-harness'
|
||||
|
||||
overrideBridgeWebContentsLookup(AgentBrowserBridge.prototype, webContentsFromIdMock)
|
||||
|
||||
// agent-browser only reaches Orca's proxy through AGENT_BROWSER_CDP; `--cdp <port>` drops the token path.
|
||||
describe('AgentBrowserBridge CDP endpoint contract', () => {
|
||||
let bridge: AgentBrowserBridge
|
||||
|
||||
beforeEach(() => {
|
||||
resetAgentBrowserBridgeMocks({
|
||||
webContentsFromIdMock,
|
||||
existsSyncMock,
|
||||
readFileSyncMock,
|
||||
stdinWrites,
|
||||
cdpWsProxyInstances: CdpWsProxyMock.instances
|
||||
})
|
||||
proxyStops.length = 0
|
||||
bridge = new AgentBrowserBridge(mockBrowserManager())
|
||||
bridge.setActiveTab(100)
|
||||
})
|
||||
|
||||
it('passes the tokened endpoint only through env, for batch and raw CLI spawns', async () => {
|
||||
const spawns = succeedRecordingAgentBrowserSpawns(execFileMock, stdinWrites, {
|
||||
snapshot: ''
|
||||
})
|
||||
|
||||
await bridge.snapshot()
|
||||
await bridge.exec('--cdp 4444 get title')
|
||||
|
||||
// The stale-session reset only closes a daemon; it must not name any CDP target.
|
||||
const [reset, ...commands] = spawns
|
||||
expect(reset).toMatchObject({ command: ['close'], cdp: undefined })
|
||||
expect(commands.map(({ command }) => command[0])).toEqual(['snapshot', 'get'])
|
||||
for (const spawn of commands) {
|
||||
expect(spawn.cdp).toBe('ws://127.0.0.1:9222/token-0')
|
||||
expect(spawn.args).not.toContain('--cdp')
|
||||
expect(spawn.args.join(' ')).not.toContain('token-0')
|
||||
}
|
||||
})
|
||||
})
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
import * as childProcess from 'node:child_process'
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
||||
|
||||
const { userDataPath } = vi.hoisted(() => ({ userDataPath: { current: '' } }))
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
getPath: vi.fn(() => userDataPath.current),
|
||||
getAppPath: vi.fn(() => userDataPath.current),
|
||||
isPackaged: false
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('node:child_process', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof childProcess>()
|
||||
return { ...actual, execFile: vi.fn(actual.execFile) }
|
||||
})
|
||||
|
||||
import { AgentBrowserBridge } from '../agent-browser-bridge'
|
||||
import { mockBrowserManager } from '../testing/agent-browser-bridge-test-harness'
|
||||
|
||||
let bridge: AgentBrowserBridge
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
userDataPath.current = mkdtempSync(join(tmpdir(), 'orca-agent-browser-retention-'))
|
||||
bridge = new AgentBrowserBridge(mockBrowserManager())
|
||||
Object.defineProperty(bridge, 'agentBrowserBin', { value: process.execPath, configurable: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
rmSync(userDataPath.current, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
function run(script: string): Promise<string> {
|
||||
return bridge['runAgentBrowserRaw']('orca-tab-retention', ['-e', script])
|
||||
}
|
||||
|
||||
function rememberChild(script: string) {
|
||||
const spawn = vi.mocked(childProcess.execFile)
|
||||
const pending = run(script)
|
||||
const result = spawn.mock.results[0]
|
||||
if (result?.type !== 'return') {
|
||||
throw new Error('Expected a real helper process')
|
||||
}
|
||||
const child = new WeakRef(result.value)
|
||||
// Mock call records must not keep the completed helper alive.
|
||||
spawn.mockClear()
|
||||
return { pending, child }
|
||||
}
|
||||
|
||||
async function collect(): Promise<void> {
|
||||
if (!global.gc) {
|
||||
throw new Error('This retention test requires --expose-gc')
|
||||
}
|
||||
for (let turn = 0; turn < 5; turn++) {
|
||||
await new Promise<void>((resolve) => setImmediate(resolve))
|
||||
global.gc()
|
||||
}
|
||||
}
|
||||
|
||||
it('releases the completed helper before the fallback deadline while its bridge stays usable', async () => {
|
||||
const { pending, child } = rememberChild("process.stdout.write('complete')")
|
||||
expect(await pending).toBe('complete')
|
||||
await collect()
|
||||
expect(child.deref()).toBeUndefined()
|
||||
expect(await run("process.stdout.write('next')")).toBe('next')
|
||||
})
|
||||
@@ -1,75 +0,0 @@
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { ModelManager } from './model-manager'
|
||||
import { openElectronSpeechDownloadRequest } from '../host/electron-speech-services'
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
getPath: () => '/tmp/orca-speech-models-test'
|
||||
}
|
||||
}))
|
||||
|
||||
type ModelManagerInternals = {
|
||||
updateState: (
|
||||
modelId: string,
|
||||
status: 'not-downloaded' | 'downloading' | 'extracting' | 'ready' | 'error',
|
||||
progress?: number,
|
||||
error?: string
|
||||
) => void
|
||||
}
|
||||
|
||||
describe('ModelManager progress callbacks', () => {
|
||||
it('unsubscribes progress callbacks without replacing other listeners', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'orca-model-manager-'))
|
||||
try {
|
||||
const manager = new ModelManager(dir, openElectronSpeechDownloadRequest)
|
||||
const internals = manager as unknown as ModelManagerInternals
|
||||
const first = vi.fn()
|
||||
const second = vi.fn()
|
||||
const clearFirst = manager.setProgressCallback(first)
|
||||
const clearSecond = manager.setProgressCallback(second)
|
||||
|
||||
internals.updateState('model-a', 'downloading', 0.25)
|
||||
clearFirst()
|
||||
internals.updateState('model-a', 'extracting')
|
||||
clearSecond()
|
||||
internals.updateState('model-a', 'ready')
|
||||
|
||||
expect(first).toHaveBeenCalledTimes(1)
|
||||
expect(first).toHaveBeenCalledWith('model-a', 0.25)
|
||||
expect(second).toHaveBeenCalledTimes(2)
|
||||
expect(second).toHaveBeenNthCalledWith(1, 'model-a', 0.25)
|
||||
expect(second).toHaveBeenNthCalledWith(2, 'model-a', 0.95)
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('coalesces per-chunk download progress to whole percent', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'orca-model-manager-'))
|
||||
try {
|
||||
const manager = new ModelManager(dir, openElectronSpeechDownloadRequest)
|
||||
const internals = manager as unknown as ModelManagerInternals
|
||||
const listener = vi.fn()
|
||||
manager.setProgressCallback(listener)
|
||||
|
||||
// A 500MB model over a 64KB chunk stream reports this many times.
|
||||
for (let chunk = 0; chunk < 8_000; chunk += 1) {
|
||||
internals.updateState('model-a', 'downloading', chunk / 8_000)
|
||||
}
|
||||
|
||||
expect(listener.mock.calls.map(([, progress]) => progress)).toEqual(
|
||||
Array.from({ length: 101 }, (_unused, percent) => percent / 100)
|
||||
)
|
||||
const afterDownload = listener.mock.calls.length
|
||||
internals.updateState('model-a', 'extracting')
|
||||
internals.updateState('model-a', 'ready')
|
||||
internals.updateState('model-a', 'ready')
|
||||
expect(listener.mock.calls.length).toBe(afterDownload + 3)
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -64,15 +64,6 @@ describe('openNodeSpeechDownloadRequest', () => {
|
||||
expect(listeners.response).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('hands any other response to the download', () => {
|
||||
const handle = openNodeSpeechDownloadRequest('https://models.example/a.tar.bz2')
|
||||
const listeners = { error: vi.fn(), response: vi.fn(), redirect: vi.fn() }
|
||||
handle.listen(listeners)
|
||||
const ok = incoming(206, { 'content-range': 'bytes 10-19/20' })
|
||||
lastRequest().emit('response', ok)
|
||||
expect(listeners.response).toHaveBeenCalledWith(ok)
|
||||
})
|
||||
|
||||
// Abort emits after detach; an unhandled ClientRequest error crashes orcad.
|
||||
it('swallows the error abort raises after the download detached', () => {
|
||||
const handle = openNodeSpeechDownloadRequest('https://models.example/a.tar.bz2')
|
||||
|
||||
@@ -46,15 +46,6 @@ function writeStoredOpenAiKey(value: string): void {
|
||||
}
|
||||
|
||||
describe('OpenAI speech API key store', () => {
|
||||
it('checks configured status without decrypting or touching safeStorage', async () => {
|
||||
writeStoredOpenAiKey('encrypted-key')
|
||||
const store = await loadStoreModule()
|
||||
|
||||
expect(store.hasOpenAiSpeechApiKey()).toBe(true)
|
||||
expect(safeStorageMock.isEncryptionAvailable).not.toHaveBeenCalled()
|
||||
expect(safeStorageMock.decryptString).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #31174: orcad cannot open a key the desktop sealed with safeStorage, so it must not say ready.
|
||||
it('reports a stored key this host cannot decrypt as unusable', async () => {
|
||||
writeStoredOpenAiKey('v10\u0000ÿþ sealed by another store')
|
||||
@@ -77,13 +68,4 @@ describe('OpenAI speech API key store', () => {
|
||||
expect(store.canReadOpenAiSpeechApiKey()).toBe(false)
|
||||
expect(() => store.readOpenAiSpeechApiKey()).toThrow('OpenAI API key could not be decrypted')
|
||||
})
|
||||
|
||||
it('caches the decrypted key so repeated dictations do not repeatedly touch safeStorage', async () => {
|
||||
writeStoredOpenAiKey('encrypted-key')
|
||||
const store = await loadStoreModule()
|
||||
|
||||
expect(store.readOpenAiSpeechApiKey()).toBe('encrypted-key')
|
||||
expect(store.readOpenAiSpeechApiKey()).toBe('encrypted-key')
|
||||
expect(safeStorageMock.decryptString).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,10 +21,4 @@ describe('resampleToRate', () => {
|
||||
expect(second[1]).toBeCloseTo(0.00625, 6)
|
||||
expect(second[80]).toBeCloseTo(0.5, 6)
|
||||
})
|
||||
|
||||
it('keeps same-rate chunks unchanged', () => {
|
||||
const samples = new Float32Array([0, 0.5, -0.5])
|
||||
|
||||
expect(resampleToRate(samples, 16000, 16000)).toBe(samples)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -100,27 +100,6 @@ describe('buildAgentRowLineageTree', () => {
|
||||
})
|
||||
|
||||
describe('unreachable lineage cleanup', () => {
|
||||
it('bounds pane-key reads while flattening disconnected cycles', () => {
|
||||
let paneKeyReads = 0
|
||||
const root = makeRow('root')
|
||||
const cycles = Array.from({ length: 200 }, (_, index) => {
|
||||
const row = makeRow(`cycle-${index}`, { parentPaneKey: `cycle-${index ^ 1}` })
|
||||
Object.defineProperty(row, 'paneKey', {
|
||||
get() {
|
||||
paneKeyReads++
|
||||
return `cycle-${index}`
|
||||
}
|
||||
})
|
||||
return row
|
||||
})
|
||||
const tree = buildAgentRowLineageTree([root, ...cycles])
|
||||
const measuredReads = paneKeyReads
|
||||
expect(tree.rootRows).toEqual([root, ...cycles])
|
||||
expect(tree.childrenByParentPaneKey.size).toBe(0)
|
||||
expect(tree.childPaneKeys.size).toBe(0)
|
||||
expect(measuredReads).toBeLessThanOrEqual(cycles.length * 20)
|
||||
})
|
||||
|
||||
it('preserves reachable edges and promotes the first disconnected duplicate in input order', () => {
|
||||
const root = makeRow('root')
|
||||
const child = makeRow('child', { parentPaneKey: 'root' })
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
agentStatusSubjectsEqual,
|
||||
makePtyAgentStatusSubject,
|
||||
makePtyRunAgentStatusSubject,
|
||||
makeStructuredAgentStatusSubject,
|
||||
serializeAgentStatusSubject,
|
||||
type AgentStatusExecutionScope
|
||||
} from './agent-status-subject'
|
||||
const SESSION_ID = 'session_11111111-1111-4111-8111-111111111111'
|
||||
|
||||
function scope(overrides: Partial<AgentStatusExecutionScope> = {}): AgentStatusExecutionScope {
|
||||
return {
|
||||
executionHostId: 'local',
|
||||
wslDistro: null,
|
||||
workspaceId: 'workspace-1',
|
||||
workspaceKind: 'git-worktree',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('agent status subjects', () => {
|
||||
it('isolates identical identities across scope and subject kind', () => {
|
||||
const identity = 'identity-1'
|
||||
const subjects = [
|
||||
makePtyRunAgentStatusSubject(scope(), identity),
|
||||
makePtyRunAgentStatusSubject(scope({ wslDistro: 'Ubuntu' }), identity),
|
||||
makePtyRunAgentStatusSubject(scope({ executionHostId: 'ssh:target-a' }), identity),
|
||||
makePtyRunAgentStatusSubject(scope({ executionHostId: 'runtime:peer-a' }), identity),
|
||||
makePtyRunAgentStatusSubject(scope({ workspaceId: 'workspace-2' }), identity),
|
||||
makePtyRunAgentStatusSubject(scope({ workspaceKind: 'folder' }), identity),
|
||||
makePtyAgentStatusSubject(scope(), identity),
|
||||
makeStructuredAgentStatusSubject(scope(), identity)
|
||||
]
|
||||
|
||||
expect(new Set(subjects.map(serializeAgentStatusSubject))).toHaveLength(subjects.length)
|
||||
for (const [index, subject] of subjects.entries()) {
|
||||
for (const other of subjects.slice(index + 1)) {
|
||||
expect(agentStatusSubjectsEqual(subject, other)).toBe(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the existing structured-session encoding byte-for-byte stable', () => {
|
||||
const serialized = serializeAgentStatusSubject(
|
||||
makeStructuredAgentStatusSubject(scope(), SESSION_ID)
|
||||
)
|
||||
|
||||
expect(serialized).toBe(
|
||||
'agent-status-subject-v1:["structured-session","local",null,"workspace-1","git-worktree","session_11111111-1111-4111-8111-111111111111"]'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -43,13 +43,4 @@ describe('createAgentStatusTracker', () => {
|
||||
tracker.handleTitle('Cursor ready')
|
||||
expect(onBecameIdle).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not fire when Factory Droid reports needs input during a working turn', () => {
|
||||
const onBecameIdle = vi.fn()
|
||||
const tracker = createAgentStatusTracker(onBecameIdle)
|
||||
|
||||
tracker.handleTitle('⠋ Droid')
|
||||
tracker.handleTitle('Factory Droid needs input')
|
||||
expect(onBecameIdle).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { hermesNativeTitleStatus } from './hermes-terminal-title'
|
||||
|
||||
describe('Hermes native session title markers', () => {
|
||||
it.each([
|
||||
['hermes', '✓ Run Orca dispatched worker … · grok-4.7 · ~/workspace', 'idle'],
|
||||
['hermes', '⏳ Ready to deploy', 'working'],
|
||||
['hermes', '⚠ Finished the changes', 'permission'],
|
||||
['hermes', '⚠️ Waiting for approval', 'permission'],
|
||||
['hermes', 'user@host: ~/workspace', null],
|
||||
['hermes', '✓not a status marker', null],
|
||||
['codex', '✓ Run Orca dispatched worker …', null],
|
||||
[null, '✓ Run Orca dispatched worker …', null]
|
||||
] as const)('classifies %s title %s as %s', (agent, title, expected) => {
|
||||
expect(hermesNativeTitleStatus(title, agent)).toBe(expected)
|
||||
})
|
||||
})
|
||||
@@ -33,30 +33,4 @@ describe('createAgentStatusOscProcessor', () => {
|
||||
expect(lastOffset, `split ${split}`).toBe(expected.lastPayloadCleanOffset)
|
||||
}
|
||||
})
|
||||
|
||||
it('applies the pending cap only to incomplete frames', () => {
|
||||
const marker = '\x1b]9999;{"state":"working"}'
|
||||
const atCap = marker + ' '.repeat(64 * 1024 - marker.length)
|
||||
const retained = createAgentStatusOscProcessor()
|
||||
expect(retained(`before${atCap}`).cleanData).toBe('before')
|
||||
expect(retained('\x1b')).toEqual({ cleanData: '', payloads: [], lastPayloadCleanOffset: null })
|
||||
expect(retained('\\after').cleanData).toBe('\\after')
|
||||
|
||||
const exact = createAgentStatusOscProcessor()
|
||||
exact(atCap)
|
||||
expect(exact('\x07after')).toEqual({
|
||||
cleanData: 'after',
|
||||
payloads: [{ state: 'working', prompt: '' }],
|
||||
lastPayloadCleanOffset: 0,
|
||||
payloadCleanOffsets: [0]
|
||||
})
|
||||
|
||||
const complete = createAgentStatusOscProcessor()
|
||||
expect(complete(`${atCap} \x1b\\after`)).toEqual({
|
||||
cleanData: 'after',
|
||||
payloads: [{ state: 'working', prompt: '' }],
|
||||
lastPayloadCleanOffset: 0,
|
||||
payloadCleanOffsets: [0]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user