mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 18:17:36 +00:00
* fix(opencode): read the binder's session store on the foreign SQLite reader worker (STA-9122) Before: the OpenCode session binder listed new sessions from opencode.db with node:sqlite on the main thread every 60 s (and on SessionStart kicks), so a large or contended store could stall the app the same way Cursor's did. After: the read is a pure openCodeBinderSessions reader in foreign-sqlite-readers/readers/, run only on the worker. The binder's correlation, pane snapshot and process sweep stay where they were. - The binder round awaits listSessions and re-checks its generation right after, so a stop() during the read discards the round before it touches the unbound map or the watermark. - The client's in-flight dedupe key now includes the cursor, so a stale round from before a restart cannot hand its rows to the restarted round. - Idle teardown is per reader. The binder lane keeps its thread for 120 s, longer than its 60 s poll, so the thread is not respawned every round. - A timeout, crash, malformed reply or unstartable worker resolves to [] (no sessions), the value the old read already returned on failure. - An absent store still reads as [] without a log line, and a permission or corrupt-file failure still logs (kept from #24577, now in the reader: it stats the path and throws anything but ENOENT/ENOTDIR to the client's log). - The binder lane inherits #24572's limits from the shared lane: no respawn until a timed-out worker has exited, 2 consecutive deaths, a queue cap of 8. Its timeout stays 60 s, matching its poll. - dispatch switches on the destructured kind, so a new kind without a case still fails to compile. orcad: the hook server runs there too, so orcad now ships foreign-sqlite-reader-entry.js beside orcad.js (ORCAD_ARTIFACTS, built as an orcad child). build-orcad runs a smoke check that starts the built worker under the build's Node and under the pinned runtime, and does a real binder read on a fixture DB, a Cursor read of a missing file and an OpenCode history list. The OpenCode history scanner uses the same entry and was bundled into orcad without it, so on orcad it always failed closed; it can now run. Tests: reader (cursor, same-ms ids, OpenCode 2 rows, missing then created, corrupt, inaccessible directory), retirement gate for the binder lane, dispatch routing, client lane (rows, failure -> [], dedupe per cursor, own thread, idle teardown default and override), binder loop with an async listSessions (failure -> [], stop during the read), orcad path resolution through orcad's host adapters, artifact list, and the smoke check against good, missing and non-reading entries. * test(opencode): cover the binder read deadline with fake timers and name the failure test accurately (STA-9122)
49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import { existsSync } from 'node:fs'
|
|
import { Worker } from 'node:worker_threads'
|
|
import { currentWorkerEntryLayout } from '../worker-thread-entry-path'
|
|
import type { CursorDesktopProfileReadResult } from './cursor-profile-result'
|
|
import { ForeignSqliteReaderClient } from './foreign-sqlite-reader-client'
|
|
import { resolveForeignSqliteReaderEntryPath } from './foreign-sqlite-reader-entry-path'
|
|
import type { BinderSessionRow, OpenCodeSessionCursor } from './opencode-binder-sessions-result'
|
|
|
|
// Why: owns the process-wide client and the real worker factory, so the client
|
|
// class stays testable with a fake factory and callers see only plain functions.
|
|
|
|
function defaultWorkerFactory(): Worker {
|
|
const workerPath = resolveForeignSqliteReaderEntryPath(currentWorkerEntryLayout(__dirname))
|
|
// A missing entry (e.g. a host whose build omits it) throws here so reads fail closed.
|
|
if (!existsSync(workerPath)) {
|
|
throw new Error(`Foreign SQLite reader entry not found: ${workerPath}`)
|
|
}
|
|
return new Worker(workerPath)
|
|
}
|
|
|
|
let sharedClient: ForeignSqliteReaderClient | null = null
|
|
|
|
function getSharedClient(): ForeignSqliteReaderClient {
|
|
sharedClient ??= new ForeignSqliteReaderClient({ workerFactory: defaultWorkerFactory })
|
|
return sharedClient
|
|
}
|
|
|
|
/**
|
|
* Read the Cursor IDE's stored session on the foreign SQLite reader worker.
|
|
* @param dbPath - Cursor's state.vscdb.
|
|
* @returns `missing`, `ok`, or `error` (also when the worker cannot answer).
|
|
*/
|
|
export function readCursorDesktopProfile(dbPath: string): Promise<CursorDesktopProfileReadResult> {
|
|
return getSharedClient().readCursorProfile(dbPath)
|
|
}
|
|
|
|
/**
|
|
* List OpenCode 1 sessions newer than `cursor` on the foreign SQLite reader worker.
|
|
* @param dbPath - The shared server's opencode.db.
|
|
* @param cursor - Store position the binder has handled up to.
|
|
* @returns Rows oldest first; `[]` when the store or the worker cannot answer.
|
|
*/
|
|
export function readOpenCodeBinderSessions(
|
|
dbPath: string,
|
|
cursor: OpenCodeSessionCursor
|
|
): Promise<BinderSessionRow[]> {
|
|
return getSharedClient().readOpenCodeBinderSessions(dbPath, cursor)
|
|
}
|