mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
326 lines
11 KiB
JavaScript
326 lines
11 KiB
JavaScript
const { app } = require('electron')
|
|
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs/promises')
|
|
const { existsSync, appendFileSync } = require('node:fs')
|
|
const { join } = require('node:path')
|
|
const { DatabaseSync } = require('node:sqlite')
|
|
const output = process.argv[2]
|
|
const phase = process.argv[3]
|
|
const profile = join(output, 'profile')
|
|
app.setPath('userData', profile)
|
|
app.setPath('sessionData', join(profile, 'chromium'))
|
|
app.disableHardwareAcceleration()
|
|
globalThis.__fileIdChildren = []
|
|
const report = {
|
|
phase,
|
|
pid: process.pid,
|
|
execPath: process.execPath,
|
|
versions: process.versions,
|
|
stages: [],
|
|
children: [],
|
|
stderr: [],
|
|
ipc: []
|
|
}
|
|
globalThis.__fileIdObserveChild = (child) => {
|
|
globalThis.__fileIdChildren.push(child)
|
|
child.stderr.on('data', (chunk) => report.stderr.push(String(chunk)))
|
|
child.on('message', (message) =>
|
|
report.ipc.push({ pid: child.pid, type: message.type, operation: message.operation })
|
|
)
|
|
}
|
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
const sessionId = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'
|
|
const markers = {
|
|
initial: 'quartzinitialmarker',
|
|
append: 'cobaltappendedmarker',
|
|
replacement: 'velvetreplacedmarker',
|
|
disabled: 'tungstendisabledmarker'
|
|
}
|
|
let api
|
|
let registration
|
|
let transport
|
|
const endpoint = `\\\\.\\pipe\\orca-file-id-${process.pid}`
|
|
let settings = {}
|
|
let transcript
|
|
const database = join(profile, 'ai-vault', 'session-search.sqlite')
|
|
function record(stage, data = {}) {
|
|
const entry = { stage, at: Date.now(), ...data }
|
|
report.stages.push(entry)
|
|
appendFileSync(join(output, `${phase}-stages.jsonl`), `${JSON.stringify(entry)}\n`)
|
|
console.log(stage, JSON.stringify(data))
|
|
}
|
|
function inspect(marker) {
|
|
const db = new DatabaseSync(database, { readOnly: true })
|
|
try {
|
|
const query = db.prepare(
|
|
'SELECT CAST(ino AS TEXT) AS ino, byte_offset, size_bytes, mtime_ms FROM files WHERE path = ?'
|
|
)
|
|
const rows = db
|
|
.prepare(`SELECT s.session_id FROM messages_fts f JOIN messages m ON m.id=f.rowid
|
|
JOIN sessions s ON s.id=m.session_row_id WHERE messages_fts MATCH ?`)
|
|
.all(marker)
|
|
return { file: query.get(transcript), sessions: rows.map((row) => row.session_id) }
|
|
} finally {
|
|
db.close()
|
|
}
|
|
}
|
|
async function query(marker, present) {
|
|
const result = await api.runProcess({
|
|
program: process.env.ORCA_FILE_ID_NODE,
|
|
args: [
|
|
join(output, 'client.cjs'),
|
|
endpoint,
|
|
JSON.stringify({ query: `"${marker}"`, scope: 'conversation', freshness: 'indexed' })
|
|
],
|
|
env: process.env,
|
|
timeoutMs: 15_000
|
|
})
|
|
assert.equal(result.code, 0, result.stderr)
|
|
const external = JSON.parse(result.stdout)
|
|
assert.equal(external.response.ok, true, JSON.stringify(external))
|
|
const response = external.response.result
|
|
assert.equal(response.kind, 'results', JSON.stringify(response))
|
|
assert.equal(
|
|
response.hits.some((hit) => hit.sessionId === sessionId),
|
|
present,
|
|
`RPC ${marker}`
|
|
)
|
|
const disk = inspect(marker)
|
|
assert.equal(disk.sessions.includes(sessionId), present, `DB ${marker}`)
|
|
const actual = await fs.stat(transcript)
|
|
assert.equal(
|
|
disk.file.byte_offset,
|
|
actual.size,
|
|
'Committed cursor covers actual transcript bytes'
|
|
)
|
|
assert.equal(disk.file.size_bytes, actual.size)
|
|
assert.equal(disk.file.mtime_ms, actual.mtimeMs)
|
|
record('query', { marker, present, clientPid: external.pid, endpoint, response, disk })
|
|
}
|
|
async function status() {
|
|
return api.rpc('aiVault.searchStatus', {})
|
|
}
|
|
async function waitFor(predicate, label, timeout = 30_000) {
|
|
const until = Date.now() + timeout
|
|
while (Date.now() < until) {
|
|
const current = await status()
|
|
if (predicate(current)) {
|
|
return current
|
|
}
|
|
await sleep(200)
|
|
}
|
|
throw new Error(`Timed out: ${label}`)
|
|
}
|
|
async function policy(next) {
|
|
const before = settings
|
|
settings = next
|
|
await fs.writeFile(join(profile, 'fixture-settings.json'), JSON.stringify(settings))
|
|
api.applySessionSearchSettingsChange(before, settings)
|
|
}
|
|
async function stopChild() {
|
|
const children = [...globalThis.__fileIdChildren]
|
|
api.resetAiVaultScannerServiceForTests()
|
|
for (const child of children) {
|
|
const until = Date.now() + 5000
|
|
while (child.exitCode === null && child.signalCode === null && Date.now() < until) {
|
|
await sleep(50)
|
|
}
|
|
assert.ok(
|
|
child.exitCode !== null || child.signalCode !== null,
|
|
`Child ${child.pid} did not exit`
|
|
)
|
|
if (!report.children.some((row) => row.pid === child.pid)) {
|
|
report.children.push({
|
|
pid: child.pid,
|
|
spawnfile: child.spawnfile,
|
|
spawnargs: child.spawnargs,
|
|
exitCode: child.exitCode,
|
|
signalCode: child.signalCode
|
|
})
|
|
}
|
|
}
|
|
}
|
|
async function run() {
|
|
assert.equal(process.env.ORCA_BACKGROUND_LAUNCH, '1')
|
|
await fs.mkdir(profile, { recursive: true })
|
|
api = require('./production.js')
|
|
api.setAppEnvironment({
|
|
getPath: (name) => (name === 'userData' ? profile : app.getPath(name)),
|
|
getAppPath: () => output,
|
|
getVersion: () => app.getVersion(),
|
|
isPackaged: () => false,
|
|
onWillQuit: (handler) => app.on('will-quit', handler),
|
|
exit: (code) => app.exit(code),
|
|
getAppMetrics: () => app.getAppMetrics()
|
|
})
|
|
const dispatcher = new api.RpcDispatcher({
|
|
methods: api.AI_VAULT_METHODS,
|
|
runtime: { getRuntimeId: () => `file-id-${process.pid}` }
|
|
})
|
|
transport = new api.UnixSocketTransport({ endpoint, kind: 'named-pipe' })
|
|
transport.onMessage((message, reply) => {
|
|
void dispatcher
|
|
.dispatch(JSON.parse(message))
|
|
.then((response) => reply(JSON.stringify(response)))
|
|
})
|
|
await transport.start()
|
|
record('rpc-transport', {
|
|
endpoint,
|
|
auth: 'fixture; production auth/metadata server not booted',
|
|
methods: 'production AI_VAULT_METHODS',
|
|
dispatcher: 'production RpcDispatcher'
|
|
})
|
|
const roots = {
|
|
...api.isolatedScanRoots(join(output, 'roots')),
|
|
wslHomeDirs: [],
|
|
additionalCodexSessionsDirs: [],
|
|
executionHostId: 'local'
|
|
}
|
|
process.env.ORCA_FILE_ID_ROOTS = JSON.stringify(roots)
|
|
await fs.mkdir(roots.claudeProjectsDir, { recursive: true })
|
|
transcript = join(roots.claudeProjectsDir, `${sessionId}.jsonl`)
|
|
if (phase === 'restart') {
|
|
settings = JSON.parse(await fs.readFile(join(profile, 'fixture-settings.json'), 'utf8'))
|
|
assert.equal(settings.aiVaultSearch.enabled, true)
|
|
const beforeRestart = inspect(markers.replacement)
|
|
registration = api.installChildSessionSearchService({
|
|
dataRoot: profile,
|
|
getSettings: () => settings
|
|
})
|
|
await waitFor((s) => s.filesIndexed === 1 && s.phase === 'current', 'Electron host restart')
|
|
await query(markers.append, false)
|
|
await query(markers.replacement, true)
|
|
assert.deepEqual(inspect(markers.replacement), beforeRestart)
|
|
record('app-host-restart-same-db')
|
|
await disableAndCheck()
|
|
return
|
|
}
|
|
const body = `${api.claudeLines([markers.initial], sessionId, 0).join('\n')}\n`
|
|
await fs.writeFile(transcript, body)
|
|
let raw = await fs.stat(transcript, { bigint: true })
|
|
for (let n = 0; raw.ino <= BigInt(Number.MAX_SAFE_INTEGER) && n < 512; n++) {
|
|
await fs.unlink(transcript)
|
|
await fs.writeFile(transcript, body)
|
|
raw = await fs.stat(transcript, { bigint: true })
|
|
}
|
|
assert.ok(raw.ino > BigInt(Number.MAX_SAFE_INTEGER), 'NTFS must supply an actual unsafe inode')
|
|
record('fixture', {
|
|
transcript,
|
|
rawIno: raw.ino.toString(),
|
|
numericIno: BigInt(Number(raw.ino)).toString()
|
|
})
|
|
registration = api.installChildSessionSearchService({
|
|
dataRoot: profile,
|
|
getSettings: () => settings
|
|
})
|
|
assert.ok(registration)
|
|
assert.equal((await status()).enabled, false)
|
|
assert.deepEqual(await api.rpc('aiVault.searchSessions', { query: markers.initial }), {
|
|
kind: 'unavailable',
|
|
reason: 'disabled'
|
|
})
|
|
assert.equal(existsSync(database), false)
|
|
record('default-off')
|
|
await policy({ aiVaultSearch: { enabled: true, historyDays: null } })
|
|
const initial = await waitFor(
|
|
(s) => s.filesIndexed === 1 && s.phase === 'current',
|
|
'initial indexing'
|
|
)
|
|
assert.equal(inspect(markers.initial).file.ino, BigInt(Number(raw.ino)).toString())
|
|
await query(markers.initial, true)
|
|
record('initial-indexing', { status: initial })
|
|
let recent = initial
|
|
for (let n = 0; n < 2; n++) {
|
|
const previous = recent
|
|
recent = await waitFor(
|
|
(s) => s.lastReconcileAt > previous.lastReconcileAt,
|
|
'recent timer reconciliation'
|
|
)
|
|
assert.equal(
|
|
recent.lastSweepCompletedAt,
|
|
initial.lastSweepCompletedAt,
|
|
'Expected a recent pass'
|
|
)
|
|
await query(markers.initial, true)
|
|
record('recent-pass', { status: recent })
|
|
}
|
|
for (let n = 0; n < 2; n++) {
|
|
await api.reconcileSessionSearchInService()
|
|
await query(markers.initial, true)
|
|
record('full-pass', { status: await status() })
|
|
}
|
|
await fs.appendFile(
|
|
transcript,
|
|
`${api.claudeLines([markers.append], sessionId, 10).join('\n')}\n`
|
|
)
|
|
const frozen = new Date(Math.floor((await fs.stat(transcript)).mtimeMs))
|
|
await fs.utimes(transcript, frozen, frozen)
|
|
await api.reconcileSessionSearchInService()
|
|
await query(markers.append, true)
|
|
const before = await fs.stat(transcript)
|
|
const replacement = `${transcript}.replacement`
|
|
const replacementBody = (await fs.readFile(transcript, 'utf8')).replaceAll(
|
|
markers.append,
|
|
markers.replacement
|
|
)
|
|
assert.equal(Buffer.byteLength(replacementBody), before.size)
|
|
await fs.writeFile(replacement, replacementBody)
|
|
await fs.rename(replacement, transcript)
|
|
await fs.utimes(transcript, before.atime, before.mtime)
|
|
const after = await fs.stat(transcript)
|
|
assert.equal(after.size, before.size)
|
|
assert.equal(after.mtimeMs, before.mtimeMs)
|
|
assert.notEqual(after.ino, before.ino)
|
|
await api.reconcileSessionSearchInService()
|
|
await query(markers.append, false)
|
|
await query(markers.replacement, true)
|
|
record('replacement', { beforeIno: before.ino, afterIno: after.ino })
|
|
await stopChild()
|
|
await waitFor((s) => s.filesIndexed === 1 && s.phase === 'current', 'scanner restart')
|
|
await query(markers.append, false)
|
|
await query(markers.replacement, true)
|
|
record('service-restart')
|
|
}
|
|
async function disableAndCheck() {
|
|
await policy({ aiVaultSearch: { enabled: false, historyDays: null } })
|
|
await waitFor((s) => !s.enabled, 'disable')
|
|
const snapshot = inspect(markers.replacement)
|
|
await fs.appendFile(
|
|
transcript,
|
|
`${api.claudeLines([markers.disabled], sessionId, 20).join('\n')}\n`
|
|
)
|
|
await api.reconcileSessionSearchInService()
|
|
await sleep(21_000)
|
|
assert.deepEqual(await api.rpc('aiVault.searchSessions', { query: markers.disabled }), {
|
|
kind: 'unavailable',
|
|
reason: 'disabled'
|
|
})
|
|
assert.deepEqual(inspect(markers.replacement), snapshot)
|
|
assert.deepEqual(inspect(markers.disabled).sessions, [])
|
|
record('disabled-no-indexing')
|
|
}
|
|
app.whenReady().then(async () => {
|
|
try {
|
|
await run()
|
|
report.ok = true
|
|
} catch (error) {
|
|
report.ok = false
|
|
report.error = error.stack
|
|
console.error(error)
|
|
} finally {
|
|
registration?.dispose()
|
|
await transport?.stop()
|
|
if (api) {
|
|
await stopChild().catch((error) => {
|
|
report.cleanupError = String(error)
|
|
report.ok = false
|
|
})
|
|
}
|
|
if (report.stderr.some((text) => text.includes('[ai-vault-search]'))) {
|
|
report.ok = false
|
|
}
|
|
await fs.writeFile(join(output, `report-${phase}.json`), JSON.stringify(report, null, 2))
|
|
app.exit(report.ok ? 0 : 1)
|
|
}
|
|
})
|