mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix(omp): find relocated conversations in relay history (#20638)
* fix(omp): resolve relay history roots on the execution host * refactor(ai-vault): inline fixed Pi and OMP root segments * refactor(ai-vault): keep fixed root segments compact
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# OMP relay history roots
|
||||
|
||||
The relay's history sidecar now resolves OMP storage on the execution host using
|
||||
`resolveOmpSessionsDir`, the same resolver as the native reader and local history
|
||||
scanner. Its environment allowlist carries only OMP root/profile inputs alongside
|
||||
the existing runtime variables. Node loader flags and unrelated credentials remain
|
||||
excluded.
|
||||
|
||||
The selected root replaces the legacy OMP source; it does not add a second scan.
|
||||
An invalid profile/root disables that source rather than selecting another store.
|
||||
The same bounded discovery, cancellation, child partition and parse cache remain.
|
||||
There are no new subprocesses, polls, recursive scan roots or wire fields.
|
||||
|
||||
The optional scanner input is in-process only. A client performing the existing
|
||||
filesystem fallback against an older relay still uses that host's legacy root,
|
||||
without consulting the client's environment or disk. A new relay publishes the
|
||||
same session schema to old and new clients; relocated conversations become visible.
|
||||
No capability is needed to interpret those ordinary existing session rows.
|
||||
|
||||
This covers the relay process's inherited environment. A profile or root set only
|
||||
inside an individual terminal is not automatically part of that environment. It
|
||||
does not prove resolution of the original iOS timing report #18663.
|
||||
|
||||
Run the actual persistence-to-sidecar proof with a read-only OMP checkout:
|
||||
|
||||
```sh
|
||||
ORCA_BACKGROUND_LAUNCH=1 bun tests/tools/omp-relay-root-smoke.mjs /path/to/oh-my-pi
|
||||
```
|
||||
|
||||
It bundles the production service entry, launches it under native Node using the
|
||||
production environment filter, and sends the real init/list IPC messages. OMP's
|
||||
actual SessionManager writes default and named-profile XDG conversations in
|
||||
isolated home/config/data directories while a legacy directory coexists. Both are
|
||||
found by exact UUID and path. An invalid profile yields no OMP sessions. No model
|
||||
calls or visible app windows are used. This local sidecar proof is not a live SSH
|
||||
connection or Windows/Linux runtime validation.
|
||||
@@ -0,0 +1,106 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getRemoteHostPlatform } from '../ssh/ssh-remote-platform'
|
||||
import { scanRemoteAiVaultSessions } from './remote-session-scanner'
|
||||
import { MemoryRemoteProvider, jsonLines } from './remote-session-scanner-test-fixtures'
|
||||
|
||||
const home = '/home/ada'
|
||||
const legacy = `${home}/.omp/agent/sessions`
|
||||
const relocated = '/data/omp/profiles/work/sessions'
|
||||
const platform = getRemoteHostPlatform('linux-x64')
|
||||
|
||||
function fixture() {
|
||||
const provider = new MemoryRemoteProvider()
|
||||
for (const [root, id] of [
|
||||
[legacy, 'legacy'],
|
||||
[relocated, 'relocated']
|
||||
]) {
|
||||
provider.addFile(
|
||||
`${root}/folder/${id}.jsonl`,
|
||||
jsonLines([
|
||||
{ type: 'session', id, cwd: '/folder workspace', timestamp: '2026-09-14T10:00:00Z' },
|
||||
{ type: 'message', message: { role: 'user', content: id } }
|
||||
]),
|
||||
70
|
||||
)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
afterEach(() => vi.unstubAllEnvs())
|
||||
|
||||
describe('host-resolved OMP remote scan roots', () => {
|
||||
it('scans the host-selected root once instead of the coexisting legacy store', async () => {
|
||||
const provider = fixture()
|
||||
const result = await scanRemoteAiVaultSessions({
|
||||
provider,
|
||||
remoteHome: home,
|
||||
hostPlatform: platform,
|
||||
executionHostId: 'ssh:box',
|
||||
ompSessionsDir: relocated
|
||||
})
|
||||
expect(result.sessions.map((row) => row.sessionId)).toEqual(['relocated'])
|
||||
expect(result.sessions[0]?.executionHostId).toBe('ssh:box')
|
||||
expect(provider.readDirPaths.filter((path) => path === relocated)).toHaveLength(1)
|
||||
expect(provider.readDirPaths).not.toContain(legacy)
|
||||
})
|
||||
|
||||
it('does not fall back when the host refuses an unsafe root', async () => {
|
||||
const provider = fixture()
|
||||
const result = await scanRemoteAiVaultSessions({
|
||||
provider,
|
||||
remoteHome: home,
|
||||
hostPlatform: platform,
|
||||
executionHostId: 'ssh:box',
|
||||
ompSessionsDir: ''
|
||||
})
|
||||
expect(result.sessions).toEqual([])
|
||||
expect(provider.readDirPaths).not.toContain(legacy)
|
||||
expect(provider.readDirPaths).not.toContain('')
|
||||
})
|
||||
|
||||
it('uses a Windows host root verbatim on a different client platform', async () => {
|
||||
const provider = new MemoryRemoteProvider()
|
||||
const root = 'D:\\OMP data\\profiles\\work\\sessions'
|
||||
const file = `${root}\\folder\\session.jsonl`
|
||||
provider.addFile(
|
||||
file,
|
||||
jsonLines([
|
||||
{
|
||||
type: 'session',
|
||||
id: 'windows',
|
||||
cwd: 'D:\\folder workspace',
|
||||
timestamp: '2026-09-14T10:00:00Z'
|
||||
},
|
||||
{ type: 'message', message: { role: 'user', content: 'Windows host' } }
|
||||
]),
|
||||
80
|
||||
)
|
||||
const result = await scanRemoteAiVaultSessions({
|
||||
provider,
|
||||
remoteHome: 'C:\\Users\\ada',
|
||||
hostPlatform: getRemoteHostPlatform('win32-x64'),
|
||||
executionHostId: 'ssh:windows',
|
||||
ompSessionsDir: root
|
||||
})
|
||||
expect(result.sessions).toHaveLength(1)
|
||||
expect(result.sessions[0]).toMatchObject({
|
||||
sessionId: 'windows',
|
||||
executionHostId: 'ssh:windows',
|
||||
executionHostPlatform: 'win32',
|
||||
cwd: 'D:\\folder workspace'
|
||||
})
|
||||
expect(provider.readDirPaths).toContain(root.replaceAll('\\', '/'))
|
||||
})
|
||||
|
||||
it('keeps legacy fallback scans independent of the client environment', async () => {
|
||||
vi.stubEnv('OMP_CODING_AGENT_DIR', relocated)
|
||||
vi.stubEnv('OMP_PROFILE', 'work')
|
||||
const result = await scanRemoteAiVaultSessions({
|
||||
provider: fixture(),
|
||||
remoteHome: home,
|
||||
hostPlatform: platform,
|
||||
executionHostId: 'ssh:box'
|
||||
})
|
||||
expect(result.sessions.map((row) => row.sessionId)).toEqual(['legacy'])
|
||||
})
|
||||
})
|
||||
@@ -17,7 +17,6 @@ import { parseHermesSessionContent } from './session-scanner-hermes-parser'
|
||||
import { partitionSubagentTranscriptPaths } from './session-scanner-subagent-transcripts'
|
||||
import { partitionOmpSubagentTranscriptPaths } from './session-scanner-omp-subagent-transcripts'
|
||||
import type { FileWithMtime } from './session-scanner-types'
|
||||
import { normalizeAgentSessionsDir } from './session-scanner-values'
|
||||
import { remoteCodexIndexedTitleReader } from './remote-session-scanner-codex-index'
|
||||
import { remoteClineSource } from './remote-session-scanner-cline-source'
|
||||
import type {
|
||||
@@ -26,6 +25,9 @@ import type {
|
||||
RemoteSessionSource
|
||||
} from './remote-session-scanner-types'
|
||||
|
||||
const PI_SESSIONS_SEGMENTS = ['.pi', 'agent', 'sessions']
|
||||
const OMP_SESSIONS_SEGMENTS = ['.omp', 'agent', 'sessions']
|
||||
|
||||
type RemoteContentParser<T = string> = (
|
||||
file: FileWithMtime,
|
||||
content: T,
|
||||
@@ -37,7 +39,8 @@ type RemoteContentParser<T = string> = (
|
||||
|
||||
export function remoteSessionSources(
|
||||
remoteHome: string,
|
||||
hostPlatform: RemoteHostPlatform
|
||||
hostPlatform: RemoteHostPlatform,
|
||||
ompSessionsDir?: string
|
||||
): RemoteSessionSource[] {
|
||||
return [
|
||||
...remoteCodexSources(remoteHome, hostPlatform),
|
||||
@@ -97,14 +100,16 @@ export function remoteSessionSources(
|
||||
['.json'],
|
||||
parseDevinSessionContent
|
||||
),
|
||||
jsonlSource('pi', remoteHome, hostPlatform, remotePiSessionsSegments(), piParser),
|
||||
{
|
||||
...jsonlSource('omp', remoteHome, hostPlatform, remoteOmpSessionsSegments(), ompParser),
|
||||
// Same posture as Claude above: OMP stores task-subagent transcripts in
|
||||
// the session's same-named artifact dir; the walk supplies counts and the
|
||||
// partition keeps the children out of the top-level list (#9330).
|
||||
partitionSubagentTranscripts: partitionOmpSubagentTranscriptPaths
|
||||
},
|
||||
jsonlSource('pi', remoteHome, hostPlatform, PI_SESSIONS_SEGMENTS, piParser),
|
||||
...(ompSessionsDir === ''
|
||||
? []
|
||||
: [
|
||||
{
|
||||
...jsonlSource('omp', remoteHome, hostPlatform, OMP_SESSIONS_SEGMENTS, ompParser),
|
||||
...(ompSessionsDir === undefined ? {} : { rootDir: ompSessionsDir }),
|
||||
partitionSubagentTranscripts: partitionOmpSubagentTranscriptPaths
|
||||
}
|
||||
]),
|
||||
jsonlSource(
|
||||
'prime-agent',
|
||||
remoteHome,
|
||||
@@ -311,14 +316,6 @@ function remotePathSegments(path: string): string[] {
|
||||
return path.replace(/\\/g, '/').split('/').filter(Boolean)
|
||||
}
|
||||
|
||||
function remotePiSessionsSegments(): string[] {
|
||||
return normalizeAgentSessionsDir('/.pi/agent/sessions', '.pi').split('/').filter(Boolean)
|
||||
}
|
||||
|
||||
function remoteOmpSessionsSegments(): string[] {
|
||||
return normalizeAgentSessionsDir('/.omp/agent/sessions', '.omp').split('/').filter(Boolean)
|
||||
}
|
||||
|
||||
// Why: remote roots are posix regardless of the client platform, so these stay literal
|
||||
// rather than round-tripping through a local-platform path join that would emit
|
||||
// backslashes on a Windows client and collapse into a single bogus segment.
|
||||
|
||||
@@ -50,6 +50,8 @@ export async function scanRemoteAiVaultSessions(args: {
|
||||
provider: RemoteSessionFilesystemProvider
|
||||
executionHostId: ExecutionHostId
|
||||
remoteHome: string
|
||||
// Host-resolved only; omission preserves legacy client-side fallback discovery.
|
||||
ompSessionsDir?: string
|
||||
hostPlatform: RemoteHostPlatform
|
||||
limit?: number
|
||||
unlimited?: boolean
|
||||
@@ -85,7 +87,7 @@ export async function scanRemoteAiVaultSessions(args: {
|
||||
const candidates = dedupeCodexRolloutFileAliases(
|
||||
(
|
||||
await mapRemoteScanBatches(
|
||||
remoteSessionSources(args.remoteHome, args.hostPlatform),
|
||||
remoteSessionSources(args.remoteHome, args.hostPlatform, args.ompSessionsDir),
|
||||
REMOTE_SCAN_CONCURRENCY,
|
||||
(source) => discoverRemoteSourceCandidates({ source, context, issues }),
|
||||
args.signal
|
||||
|
||||
@@ -118,9 +118,7 @@ describe('buildRelayAiVaultServiceEnv', () => {
|
||||
expect(env.HOME).toBe('/home/ada')
|
||||
})
|
||||
|
||||
// The sidecar takes remoteHome and hostPlatform from its init message, so an
|
||||
// agent-home override on the remote host is not part of how it finds roots.
|
||||
it('withholds the agent-home variables the desktop child needs', () => {
|
||||
it('withholds unrelated agent-home variables', () => {
|
||||
const env = buildRelayAiVaultServiceEnv(
|
||||
{ CODEX_HOME: '/remote/.codex', PATH: '/usr/bin' },
|
||||
'linux'
|
||||
@@ -135,7 +133,7 @@ describe('buildRelayAiVaultServiceEnv', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('carries OMP root/profile inputs only to the desktop service, retaining empty canonical profile', () => {
|
||||
it('carries OMP root/profile inputs to both services, retaining empty canonical profile', () => {
|
||||
const roots = {
|
||||
OMP_PROFILE: '',
|
||||
PI_PROFILE: 'work',
|
||||
@@ -144,5 +142,30 @@ it('carries OMP root/profile inputs only to the desktop service, retaining empty
|
||||
XDG_DATA_HOME: '/home/dev/data'
|
||||
}
|
||||
expect(buildAiVaultServiceEnv(roots, 'linux')).toEqual({ ...roots, ELECTRON_RUN_AS_NODE: '1' })
|
||||
expect(buildRelayAiVaultServiceEnv(roots, 'linux')).toEqual({})
|
||||
expect(buildRelayAiVaultServiceEnv(roots, 'linux')).toEqual(roots)
|
||||
})
|
||||
|
||||
it('carries Windows OMP roots case-insensitively without forwarding other agent roots', () => {
|
||||
expect(
|
||||
buildRelayAiVaultServiceEnv(
|
||||
{
|
||||
omp_profile: 'work',
|
||||
omp_coding_agent_dir: 'D:\\omp',
|
||||
pi_profile: 'old',
|
||||
pi_config_dir: '.custom',
|
||||
pi_coding_agent_dir: 'D:\\inherited',
|
||||
xdg_data_home: 'D:\\data',
|
||||
codex_home: 'D:\\codex',
|
||||
AWS_SECRET_ACCESS_KEY: 'secret'
|
||||
},
|
||||
'win32'
|
||||
)
|
||||
).toEqual({
|
||||
OMP_PROFILE: 'work',
|
||||
OMP_CODING_AGENT_DIR: 'D:\\omp',
|
||||
PI_PROFILE: 'old',
|
||||
PI_CONFIG_DIR: '.custom',
|
||||
PI_CODING_AGENT_DIR: 'D:\\inherited',
|
||||
XDG_DATA_HOME: 'D:\\data'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,6 +30,15 @@ export const RUNTIME_ENV_ALLOWLIST = [
|
||||
'NUMBER_OF_PROCESSORS'
|
||||
] as const
|
||||
|
||||
const OMP_ROOT_ENV_ALLOWLIST = [
|
||||
'OMP_CODING_AGENT_DIR',
|
||||
'OMP_PROFILE',
|
||||
'PI_CODING_AGENT_DIR',
|
||||
'PI_CONFIG_DIR',
|
||||
'PI_PROFILE',
|
||||
'XDG_DATA_HOME'
|
||||
] as const
|
||||
|
||||
// Why: the desktop child resolves agent roots from its own environment, so
|
||||
// dropping one hides every session of a user who relocated that agent's home.
|
||||
const AGENT_ROOT_ENV_ALLOWLIST = [
|
||||
@@ -39,19 +48,12 @@ const AGENT_ROOT_ENV_ALLOWLIST = [
|
||||
'DEVIN_HOME',
|
||||
'GROK_HOME',
|
||||
'KIMI_CODE_HOME',
|
||||
'OMP_CODING_AGENT_DIR',
|
||||
'OMP_PROFILE',
|
||||
'OPENCLAW_STATE_DIR',
|
||||
'OPENCODE_DB',
|
||||
'PI_CODING_AGENT_DIR',
|
||||
'PI_CONFIG_DIR',
|
||||
'PI_PROFILE',
|
||||
'PRIME_AGENT_CODING_AGENT_DIR',
|
||||
'PRIME_AGENT_CODING_AGENT_SESSION_DIR',
|
||||
'PRIME_AGENT_SESSION_DIR',
|
||||
// Why: OpenCode and migrated OMP session stores use the XDG data dir,
|
||||
// so this one is an agent root here rather than generic runtime state.
|
||||
'XDG_DATA_HOME'
|
||||
...OMP_ROOT_ENV_ALLOWLIST
|
||||
] as const
|
||||
|
||||
export function pickAllowedEnv(
|
||||
@@ -94,10 +96,10 @@ export function buildAiVaultServiceEnv(
|
||||
return env
|
||||
}
|
||||
|
||||
/** Relay: the sidecar takes every root from its init message, not the environment. */
|
||||
/** Relay: resolve OMP storage from the execution host, never the client. */
|
||||
export function buildRelayAiVaultServiceEnv(
|
||||
baseEnv: NodeJS.ProcessEnv = process.env,
|
||||
platform: NodeJS.Platform = process.platform
|
||||
): NodeJS.ProcessEnv {
|
||||
return pickAllowedEnv(RUNTIME_ENV_ALLOWLIST, baseEnv, platform)
|
||||
return pickAllowedEnv([...RUNTIME_ENV_ALLOWLIST, ...OMP_ROOT_ENV_ALLOWLIST], baseEnv, platform)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveOmpSessionsDir } from '../main/ai-vault/omp-session-root'
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../shared/execution-host'
|
||||
import { scanRemoteAiVaultSessions } from '../main/ai-vault/remote-session-scanner'
|
||||
import { readAiVaultSessionTitlesFromFiles } from '../main/ai-vault/session-title-file-reader'
|
||||
@@ -50,6 +51,10 @@ async function execute(request: RelayAiVaultServiceRequest): Promise<void> {
|
||||
provider,
|
||||
executionHostId: LOCAL_EXECUTION_HOST_ID,
|
||||
remoteHome: init.remoteHome,
|
||||
ompSessionsDir: resolveOmpSessionsDir({
|
||||
homeDir: init.remoteHome,
|
||||
platform: init.hostPlatform.os
|
||||
}),
|
||||
hostPlatform: init.hostPlatform,
|
||||
limit: request.params.limit,
|
||||
unlimited: request.params.unlimited,
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { mkdtemp, mkdir, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import { build } from 'esbuild'
|
||||
import { spawnProcess } from '../../src/shared/child-process/run-process.ts'
|
||||
import { buildRelayAiVaultServiceEnv } from '../../src/main/ai-vault/session-scanner-service-env.ts'
|
||||
import { getRemoteHostPlatform } from '../../src/main/ssh/ssh-remote-platform.ts'
|
||||
|
||||
assert.ok(process.argv[2], 'Pass a read-only OMP checkout')
|
||||
const node = Bun.which('node')
|
||||
assert.ok(node, 'A native Node executable is required')
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
const scratch = await mkdtemp(join(tmpdir(), 'orca-omp-relay-root-'))
|
||||
const home = join(scratch, 'home')
|
||||
const xdg = join(scratch, 'data')
|
||||
Object.assign(process.env, {
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
XDG_CONFIG_HOME: join(scratch, 'config'),
|
||||
XDG_DATA_HOME: xdg,
|
||||
XDG_STATE_HOME: join(scratch, 'state'),
|
||||
XDG_CACHE_HOME: join(scratch, 'cache')
|
||||
})
|
||||
for (const key of [
|
||||
'OMP_CODING_AGENT_DIR',
|
||||
'PI_CODING_AGENT_DIR',
|
||||
'OMP_PROFILE',
|
||||
'PI_PROFILE',
|
||||
'PI_CONFIG_DIR',
|
||||
'PI_CONFIG_FILES'
|
||||
]) {
|
||||
delete process.env[key]
|
||||
}
|
||||
const source = (path) => pathToFileURL(join(resolve(process.argv[2]), path)).href
|
||||
const managers = []
|
||||
const entry = join(scratch, 'relay-ai-vault-service.cjs')
|
||||
|
||||
async function scan(profile, env = process.env) {
|
||||
const child = spawnProcess({
|
||||
program: node,
|
||||
args: [entry],
|
||||
cwd: scratch,
|
||||
env: { ...buildRelayAiVaultServiceEnv(env), ORCA_BACKGROUND_LAUNCH: '1' },
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc']
|
||||
})
|
||||
let stderr = ''
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr = (stderr + data).slice(-16000)
|
||||
})
|
||||
const exited = new Promise((resolveExit) => child.once('exit', resolveExit))
|
||||
let timer
|
||||
try {
|
||||
const result = new Promise((resolveResult, reject) => {
|
||||
timer = setTimeout(() => reject(new Error(`Relay scan timed out: ${stderr}`)), 15000)
|
||||
child.once('error', reject)
|
||||
child.once('exit', (code) => reject(new Error(`Relay exited ${code}: ${stderr}`)))
|
||||
child.on('message', (message) => {
|
||||
if (message.type === 'ready') {
|
||||
child.send({ type: 'request', id: 1, operation: 'list', params: { limit: 20 } })
|
||||
} else if (message.type === 'error') {
|
||||
reject(new Error(message.message))
|
||||
} else if (message.type === 'result') {
|
||||
resolveResult(message.value)
|
||||
}
|
||||
})
|
||||
})
|
||||
child.send({
|
||||
type: 'init',
|
||||
protocol: 1,
|
||||
remoteHome: home,
|
||||
hostPlatform: getRemoteHostPlatform(`${process.platform}-${process.arch}`)
|
||||
})
|
||||
const value = await result
|
||||
assert.equal(value.issues.length, 0, JSON.stringify(value.issues))
|
||||
const sessions = value.sessions.filter((session) => session.agent === 'omp')
|
||||
if (profile === 'invalid') {
|
||||
assert.deepEqual(sessions, [])
|
||||
} else {
|
||||
const manager = managers.at(-1)
|
||||
assert.equal(sessions.length, 1)
|
||||
assert.equal(sessions[0].sessionId, manager.getSessionId())
|
||||
assert.equal(sessions[0].filePath, manager.getSessionFile())
|
||||
assert.equal(sessions[0].cwd, join(scratch, 'folder workspace'))
|
||||
}
|
||||
return { profile: profile || 'default', sessions: sessions.length, actualNodeSidecar: true }
|
||||
} finally {
|
||||
clearTimeout(timer)
|
||||
child.kill()
|
||||
await exited
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await mkdir(join(home, '.omp', 'agent', 'sessions'), { recursive: true })
|
||||
await mkdir(join(xdg, 'omp', 'profiles', 'work'), { recursive: true })
|
||||
await build({
|
||||
entryPoints: [join(root, 'src/relay/ai-vault-service-entry.ts')],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
target: 'node18',
|
||||
format: 'cjs',
|
||||
outfile: entry,
|
||||
external: ['electron'],
|
||||
define: { 'process.env.NODE_ENV': '"production"' }
|
||||
})
|
||||
const upstream = await import(source('packages/utils/src/dirs.ts'))
|
||||
const { SessionManager } = await import(
|
||||
source('packages/coding-agent/src/session/session-manager.ts')
|
||||
)
|
||||
const results = []
|
||||
for (const profile of ['', 'work']) {
|
||||
process.env.OMP_PROFILE = profile
|
||||
upstream.__resetDirsFromEnvForTests()
|
||||
const cwd = join(scratch, 'folder workspace')
|
||||
await mkdir(cwd, { recursive: true })
|
||||
const manager = SessionManager.create(cwd)
|
||||
managers.push(manager)
|
||||
manager.appendMessage({
|
||||
role: 'user',
|
||||
content: 'OMP relay history proof',
|
||||
timestamp: Date.now()
|
||||
})
|
||||
await manager.ensureOnDisk()
|
||||
await manager.flush()
|
||||
const expected = join(xdg, 'omp', ...(profile ? ['profiles', profile] : []), 'sessions')
|
||||
assert.ok(manager.getSessionFile().startsWith(expected))
|
||||
results.push(await scan(profile))
|
||||
}
|
||||
results.push(await scan('invalid', { ...process.env, OMP_PROFILE: '../invalid' }))
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
results,
|
||||
actualOmpPersistence: true,
|
||||
legacyDirectoryCoexists: true,
|
||||
modelCalls: 0
|
||||
})
|
||||
)
|
||||
} finally {
|
||||
for (const manager of managers) {
|
||||
await manager.close()
|
||||
}
|
||||
await rm(scratch, { recursive: true, force: true })
|
||||
}
|
||||
Reference in New Issue
Block a user