mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(session-history): list a chat once under its writer when pi and omp share a sessions folder (#33647)
This commit is contained in:
@@ -277,6 +277,16 @@ describe('validateAiVaultSessionDeleteTarget', () => {
|
||||
expect(result.allowed).toBe(true)
|
||||
})
|
||||
|
||||
it('allows an omp row the Pi walk labelled from under the Pi root', () => {
|
||||
const result = validateAiVaultSessionDeleteTarget({
|
||||
agent: 'omp',
|
||||
filePath: join(PI_ROOT, '-repo-app', 'session-1.jsonl'),
|
||||
executionHostId: 'local',
|
||||
rootOptions: { piSessionsDir: PI_ROOT, ompSessionsDir: OMP_ROOT }
|
||||
})
|
||||
expect(result.allowed).toBe(true)
|
||||
})
|
||||
|
||||
it('allows an uppercase extension since discovery folds extension case', () => {
|
||||
const result = validateAiVaultSessionDeleteTarget({
|
||||
agent: 'gemini',
|
||||
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
type ExecutionHostId
|
||||
} from '../../shared/execution-host'
|
||||
import { isPathInsideOrEqual } from '../../shared/cross-platform-path'
|
||||
import { AI_VAULT_AGENT_SOURCES, isDiscoverableSessionFile } from './session-scanner-agent-sources'
|
||||
import {
|
||||
AI_VAULT_AGENT_SOURCES,
|
||||
isDiscoverableSessionFile,
|
||||
sessionRowRootDirs
|
||||
} from './session-scanner-agent-sources'
|
||||
import { subagentTranscriptsDirFor } from './session-scanner-subagent-transcripts'
|
||||
import type { AiVaultScanOptions } from './session-scanner-types'
|
||||
|
||||
@@ -57,8 +61,7 @@ export function validateAiVaultSessionDeleteTarget(
|
||||
// Collapse traversal before textual containment checks.
|
||||
const resolvedPath = resolve(filePath)
|
||||
const source = AI_VAULT_AGENT_SOURCES[agent]
|
||||
const roots = source
|
||||
.rootDirs(args.rootOptions ?? {}, args.wslHomeDirs ?? [])
|
||||
const roots = sessionRowRootDirs(agent, args.rootOptions ?? {}, args.wslHomeDirs ?? [])
|
||||
// An empty root resolves to cwd and would silently allowlist it.
|
||||
.filter((rootDir) => rootDir.trim().length > 0)
|
||||
.map((rootDir) => resolve(rootDir))
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { parsePersistedSidecar } from './session-sidecar-stat'
|
||||
|
||||
// Bump for row-layout or cached-semantic changes; mismatched databases are discarded.
|
||||
export const SESSION_PARSE_CACHE_SCHEMA_VERSION = 7
|
||||
export const SESSION_PARSE_CACHE_SCHEMA_VERSION = 8
|
||||
|
||||
const SCHEMA_SQL = `
|
||||
CREATE TABLE IF NOT EXISTS meta(key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
kiroTranscriptPathForMetadata
|
||||
} from './session-scanner-kiro-parser'
|
||||
import { OPENCLAW_AGENT_SOURCE } from './openclaw-session-layout'
|
||||
import { OMP_SESSION_ARTIFACT_DIR_PATTERN } from './session-scanner-omp-subagent-transcripts'
|
||||
import { isOmpConversationDirectory } from './session-scanner-omp-subagent-transcripts'
|
||||
import {
|
||||
claudeProjectsRootDirs,
|
||||
codexHomeSessionsDir,
|
||||
@@ -231,14 +231,15 @@ export const AI_VAULT_AGENT_SOURCES: AiVaultAgentSourceTable = {
|
||||
'agent',
|
||||
'sessions'
|
||||
]),
|
||||
extensions: ['.jsonl']
|
||||
extensions: ['.jsonl'],
|
||||
// OMP inherits PI_CODING_AGENT_DIR, so Pi's folder can hold OMP's task artifacts.
|
||||
directoryPredicate: isOmpConversationDirectory
|
||||
},
|
||||
omp: {
|
||||
rootDirs: (options, wslHomeDirs) =>
|
||||
ompSessionsRootDirs({ ompSessionsDir: options.ompSessionsDir, wslHomeDirs }),
|
||||
extensions: ['.jsonl'],
|
||||
// Skip worker artifacts below workspace depth; OMP lists only top-level conversations (#9330).
|
||||
directoryPredicate: (name, depth) => depth === 0 || !OMP_SESSION_ARTIFACT_DIR_PATTERN.test(name)
|
||||
directoryPredicate: isOmpConversationDirectory
|
||||
},
|
||||
'prime-agent': {
|
||||
rootDirs: (options, wslHomeDirs) =>
|
||||
@@ -301,4 +302,16 @@ export const AI_VAULT_AGENT_SOURCES: AiVaultAgentSourceTable = {
|
||||
}
|
||||
}
|
||||
|
||||
// The Pi walk labels the OMP transcripts it reads, so an OMP row can sit under Pi's roots too.
|
||||
export function sessionRowRootDirs(
|
||||
agent: AiVaultDeletableAgent,
|
||||
options: AiVaultScanOptions,
|
||||
wslHomeDirs: readonly string[]
|
||||
): string[] {
|
||||
const roots = AI_VAULT_AGENT_SOURCES[agent].rootDirs(options, wslHomeDirs)
|
||||
return agent === 'omp'
|
||||
? [...roots, ...AI_VAULT_AGENT_SOURCES.pi.rootDirs(options, wslHomeDirs)]
|
||||
: roots
|
||||
}
|
||||
|
||||
export { isDiscoverableSessionFile } from './session-file-discovery'
|
||||
|
||||
@@ -54,14 +54,28 @@ export function dropCodexHardlinkAliases(
|
||||
return dedupeCodexRolloutFileAliases(candidates, CODEX_ALIAS_ACCESSORS)
|
||||
}
|
||||
|
||||
// OMP inherits PI_CODING_AGENT_DIR; the Pi parse labels the OMP transcripts it reads, so read each once.
|
||||
function dropOmpCandidatesListedByPi(
|
||||
candidates: readonly SessionFileCandidate[]
|
||||
): SessionFileCandidate[] {
|
||||
const piPaths = new Set(
|
||||
candidates.flatMap((candidate) => (candidate.agent === 'pi' ? [candidate.file.path] : []))
|
||||
)
|
||||
return candidates.filter(
|
||||
(candidate) => candidate.agent !== 'omp' || !piPaths.has(candidate.file.path)
|
||||
)
|
||||
}
|
||||
|
||||
export async function sessionCandidatesFromDiscoveries(
|
||||
discoveries: SessionFileDiscovery[],
|
||||
options: AiVaultScanOptions
|
||||
): Promise<SessionFileCandidate[]> {
|
||||
const candidates = await dedupeCodexRolloutAliases(
|
||||
discoveries
|
||||
.flatMap((discovery) => discoveryFileCandidates(discovery, discovery.files, options))
|
||||
.sort((left, right) => right.file.mtimeMs - left.file.mtimeMs),
|
||||
dropOmpCandidatesListedByPi(
|
||||
discoveries.flatMap((discovery) =>
|
||||
discoveryFileCandidates(discovery, discovery.files, options)
|
||||
)
|
||||
).sort((left, right) => right.file.mtimeMs - left.file.mtimeMs),
|
||||
CODEX_ALIAS_ACCESSORS,
|
||||
(filePath) => readCodexRolloutSessionMetaId(filePath, options.signal, 'scan'),
|
||||
options.signal
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { foldOmpTranscriptTitle, type OmpTranscriptTitle } from './session-scanner-omp-title'
|
||||
import { isOmpWrittenRecord } from './session-scanner-omp-written-record'
|
||||
import {
|
||||
remoteSessionContentLines,
|
||||
type RemoteSessionContent
|
||||
@@ -221,12 +222,16 @@ function consumeMessageGraphRecordLine(state: MessageGraphParseState, line: stri
|
||||
if (!record) {
|
||||
return
|
||||
}
|
||||
if (accumulator.agent === 'pi' && isOmpWrittenRecord(record)) {
|
||||
accumulator.agent = 'omp'
|
||||
accumulator.fallbackTitle = accumulator.title
|
||||
accumulator.title = null
|
||||
}
|
||||
updateTimeline(accumulator, extractString(record.timestamp))
|
||||
if (accumulator.agent === 'omp') {
|
||||
state.ompTitle = foldOmpTranscriptTitle(state.ompTitle, record)
|
||||
if (state.ompTitle) {
|
||||
accumulator.title = state.ompTitle.title
|
||||
}
|
||||
// Fold before the writer is known: an OMP title can precede the record that proves OMP wrote it.
|
||||
state.ompTitle = foldOmpTranscriptTitle(state.ompTitle, record)
|
||||
if (accumulator.agent === 'omp' && state.ompTitle) {
|
||||
accumulator.title = state.ompTitle.title
|
||||
}
|
||||
if (record.type === 'session') {
|
||||
const sessionId = extractString(record.id)
|
||||
@@ -277,8 +282,7 @@ export function createMessageGraphSessionResumeState(
|
||||
}),
|
||||
ompTitle: null
|
||||
})
|
||||
// Only OMP has this task-artifact layout; avoid directory reads for the other graph agents.
|
||||
return agent === 'omp' ? withOmpSubagentTranscriptCount(state, file.path) : state
|
||||
return withOmpSubagentTranscriptCount(state, file.path)
|
||||
}
|
||||
|
||||
function createMessageGraphResumeState(state: MessageGraphParseState): ResumableSessionParseState {
|
||||
|
||||
@@ -4,8 +4,8 @@ import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { parseMessageGraphSessionContent } from './session-scanner-graph-parsers'
|
||||
import {
|
||||
OMP_SESSION_ARTIFACT_DIR_PATTERN,
|
||||
countOmpSubagentTranscripts,
|
||||
isOmpConversationDirectory,
|
||||
partitionOmpSubagentTranscriptPaths
|
||||
} from './session-scanner-omp-subagent-transcripts'
|
||||
|
||||
@@ -24,11 +24,12 @@ async function makeWorkspaceDir(): Promise<string> {
|
||||
return root
|
||||
}
|
||||
|
||||
describe('OMP_SESSION_ARTIFACT_DIR_PATTERN', () => {
|
||||
it('matches session artifact dir names and nothing else in the layout', () => {
|
||||
expect(OMP_SESSION_ARTIFACT_DIR_PATTERN.test(SESSION_STEM)).toBe(true)
|
||||
expect(OMP_SESSION_ARTIFACT_DIR_PATTERN.test('AuthAndPreflight')).toBe(false)
|
||||
expect(OMP_SESSION_ARTIFACT_DIR_PATTERN.test('local')).toBe(false)
|
||||
describe('isOmpConversationDirectory', () => {
|
||||
it('skips session artifact dirs below the workspace and nothing else in the layout', () => {
|
||||
expect(isOmpConversationDirectory(SESSION_STEM, 0)).toBe(true)
|
||||
expect(isOmpConversationDirectory(SESSION_STEM, 1)).toBe(false)
|
||||
expect(isOmpConversationDirectory('AuthAndPreflight', 1)).toBe(true)
|
||||
expect(isOmpConversationDirectory('local', 1)).toBe(true)
|
||||
// Remote partitioning has no depth context, so encoded workspace names must not match session stems.
|
||||
for (const workspaceDirName of [
|
||||
`-${SESSION_STEM}`,
|
||||
@@ -36,7 +37,7 @@ describe('OMP_SESSION_ARTIFACT_DIR_PATTERN', () => {
|
||||
`--${SESSION_STEM}--`,
|
||||
'home-app-85dfa2f063812c58976deb581167a634a4'
|
||||
]) {
|
||||
expect(OMP_SESSION_ARTIFACT_DIR_PATTERN.test(workspaceDirName)).toBe(false)
|
||||
expect(isOmpConversationDirectory(workspaceDirName, 1)).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,7 +7,12 @@ import type { ResumableSessionParseState } from './session-scanner-types'
|
||||
// Infer lineage from stamp_UUID artifact directories: task transcripts are label-named and omit parentSession (#9330).
|
||||
const OMP_SESSION_DIR_NAME = String.raw`\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`
|
||||
|
||||
export const OMP_SESSION_ARTIFACT_DIR_PATTERN = new RegExp(`^${OMP_SESSION_DIR_NAME}$`, 'i')
|
||||
const OMP_SESSION_ARTIFACT_DIR_PATTERN = new RegExp(`^${OMP_SESSION_DIR_NAME}$`, 'i')
|
||||
|
||||
/** Walk workspace buckets but not task artifact dirs; OMP lists only top-level conversations (#9330). */
|
||||
export function isOmpConversationDirectory(name: string, depth: number): boolean {
|
||||
return depth === 0 || !OMP_SESSION_ARTIFACT_DIR_PATTERN.test(name)
|
||||
}
|
||||
|
||||
// Remote counts include only direct artifact children, matching local readdir; nested grandchildren remain excluded.
|
||||
const OMP_SUBAGENT_SUBTREE_PATTERN = new RegExp(String.raw`[\\/]${OMP_SESSION_DIR_NAME}[\\/]`, 'i')
|
||||
@@ -60,7 +65,7 @@ export function partitionOmpSubagentTranscriptPaths(
|
||||
return { sessionFilePaths, subagentTranscriptCounts }
|
||||
}
|
||||
|
||||
/** Count on the transcript-owning host only; remote walks supply their own counts. */
|
||||
/** Count OMP rows on the transcript-owning host only; remote walks supply their own counts. */
|
||||
export function withOmpSubagentTranscriptCount(
|
||||
state: ResumableSessionParseState,
|
||||
transcriptFilePath: string
|
||||
@@ -74,7 +79,7 @@ export function withOmpSubagentTranscriptCount(
|
||||
const session = await state.finalize(platform, options)
|
||||
const ownsTranscriptDisk =
|
||||
!options?.executionHostId || options.executionHostId === LOCAL_EXECUTION_HOST_ID
|
||||
if (!session || !ownsTranscriptDisk) {
|
||||
if (session?.agent !== 'omp' || !ownsTranscriptDisk) {
|
||||
return session
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { asRecord, extractString } from './session-scanner-values'
|
||||
|
||||
// OMP inherits PI_CODING_AGENT_DIR, so Pi's folder can hold OMP transcripts; Pi never writes these.
|
||||
export function isOmpWrittenRecord(record: Record<string, unknown>): boolean {
|
||||
return (
|
||||
(record.type === 'title' && record.v === 1) ||
|
||||
asRecord(record.runtime) !== null ||
|
||||
(record.type === 'model_change' &&
|
||||
record.modelId === undefined &&
|
||||
extractString(record.model) !== null)
|
||||
)
|
||||
}
|
||||
@@ -255,9 +255,9 @@ async function reuseCachedSession(
|
||||
// Zero-turn parents may never change again; recount late subagent transcripts outside the cached file signature.
|
||||
if (session && session.messageCount === 0) {
|
||||
const subagentTranscriptCount =
|
||||
candidate.agent === 'claude'
|
||||
session.agent === 'claude'
|
||||
? await countSubagentTranscripts(candidate.file.path)
|
||||
: candidate.agent === 'omp'
|
||||
: session.agent === 'omp'
|
||||
? await countOmpSubagentTranscripts(candidate.file.path)
|
||||
: null
|
||||
if (
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { scanAiVaultSessions } from './session-scanner'
|
||||
import { isolatedScanRoots, writeJsonlFile } from './session-scanner-test-fixtures'
|
||||
|
||||
const OMP_STEM = '2026-05-01T10-00-00-000Z_0199a000-0000-7000-8000-000000000002'
|
||||
const runtime = { version: '18.12.2' }
|
||||
|
||||
let tempRoots: string[] = []
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(tempRoots.map((root) => rm(root, { recursive: true, force: true })))
|
||||
tempRoots = []
|
||||
})
|
||||
|
||||
// Shapes follow what Pi 0.84 and OMP 18.12 write: OMP adds a title slot and runtime stamps.
|
||||
async function writeSharedRootTranscripts(sessionsDir: string): Promise<void> {
|
||||
const piDir = join(sessionsDir, '--repo-app--')
|
||||
await writeJsonlFile(join(piDir, '2026-05-01T09-00-00-000Z_pi.jsonl'), [
|
||||
{
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: '0199a000-0000-7000-8000-000000000001',
|
||||
timestamp: '2026-05-01T09:00:00.000Z',
|
||||
cwd: '/repo/app'
|
||||
},
|
||||
{
|
||||
type: 'model_change',
|
||||
id: 'a1',
|
||||
parentId: null,
|
||||
timestamp: '2026-05-01T09:00:00.100Z',
|
||||
provider: 'anthropic',
|
||||
modelId: 'claude-sonnet-4-5'
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
id: 'a2',
|
||||
parentId: 'a1',
|
||||
timestamp: '2026-05-01T09:00:01.000Z',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'Pi prompt' }] }
|
||||
}
|
||||
])
|
||||
const ompDir = join(sessionsDir, '-repo-app')
|
||||
await writeJsonlFile(join(ompDir, `${OMP_STEM}.jsonl`), [
|
||||
{
|
||||
type: 'title',
|
||||
v: 1,
|
||||
title: 'OMP named chat',
|
||||
source: 'user',
|
||||
updatedAt: '2026-05-01T10:00:02.000Z'
|
||||
},
|
||||
{
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: '0199a000-0000-7000-8000-000000000002',
|
||||
timestamp: '2026-05-01T10:00:00.000Z',
|
||||
cwd: '/repo/app'
|
||||
},
|
||||
{
|
||||
type: 'model_change',
|
||||
id: 'b1',
|
||||
parentId: null,
|
||||
timestamp: '2026-05-01T10:00:00.100Z',
|
||||
model: 'anthropic/claude-sonnet-4-5',
|
||||
runtime
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
id: 'b2',
|
||||
parentId: 'b1',
|
||||
timestamp: '2026-05-01T10:00:01.000Z',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'OMP prompt' }] },
|
||||
runtime
|
||||
}
|
||||
])
|
||||
// Older OMP builds wrote no title slot or runtime stamps, only the header name and model_change.model.
|
||||
await writeJsonlFile(join(ompDir, '2026-04-01T10-00-00-000Z_legacy.jsonl'), [
|
||||
{
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: '0199a000-0000-7000-8000-000000000004',
|
||||
title: 'Legacy OMP name',
|
||||
timestamp: '2026-04-01T10:00:00.000Z',
|
||||
cwd: '/repo/app'
|
||||
},
|
||||
{ type: 'model_change', timestamp: '2026-04-01T10:00:00.100Z', model: 'gpt-5.4-mini' },
|
||||
{
|
||||
type: 'message',
|
||||
timestamp: '2026-04-01T10:00:01.000Z',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'Legacy prompt' }] }
|
||||
}
|
||||
])
|
||||
await writeJsonlFile(join(ompDir, OMP_STEM, 'Worker.jsonl'), [
|
||||
{
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: '0199a000-0000-7000-8000-000000000003',
|
||||
timestamp: '2026-05-01T10:00:05.000Z',
|
||||
cwd: '/repo/app'
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
id: 'c1',
|
||||
parentId: null,
|
||||
timestamp: '2026-05-01T10:00:06.000Z',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'Worker task' }] },
|
||||
runtime
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
describe('scanAiVaultSessions with overlapping Pi and OMP roots', () => {
|
||||
const sameFolder = (sessionsDir: string): string => sessionsDir
|
||||
it.each([
|
||||
{ layout: 'the same folder', piRoot: sameFolder, scope: {} },
|
||||
// PI_CODING_AGENT_DIR=<dir>: Pi walks <dir>, OMP walks <dir>/sessions.
|
||||
{ layout: 'a parent folder', piRoot: (dir: string) => join(dir, '..'), scope: {} },
|
||||
// A one-row cap sends the rest through scope recovery: Pi's buckets and OMP's walk.
|
||||
{
|
||||
layout: 'the same folder for a workspace',
|
||||
piRoot: sameFolder,
|
||||
scope: { scopePaths: ['/repo/app'], limit: 1 }
|
||||
}
|
||||
])('lists each transcript once under its writer when Pi scans $layout', async (testCase) => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-ai-vault-pi-omp-shared-'))
|
||||
tempRoots.push(root)
|
||||
const sessionsDir = join(root, 'agent', 'sessions')
|
||||
await writeSharedRootTranscripts(sessionsDir)
|
||||
|
||||
const result = await scanAiVaultSessions({
|
||||
...isolatedScanRoots(root),
|
||||
...testCase.scope,
|
||||
piSessionsDir: testCase.piRoot(sessionsDir),
|
||||
ompSessionsDir: sessionsDir,
|
||||
platform: 'linux'
|
||||
})
|
||||
|
||||
expect(
|
||||
result.sessions
|
||||
.map((session) => [session.agent, session.title, session.subagentTranscriptCount])
|
||||
.sort()
|
||||
).toEqual([
|
||||
['omp', 'Legacy OMP name', 0],
|
||||
['omp', 'OMP named chat', 1],
|
||||
['pi', 'Pi prompt', 0]
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -77,7 +77,14 @@ export async function scanInScopeSessions(args: ScopeScanArgs): Promise<AiVaultS
|
||||
}
|
||||
args.inventory.length = 0
|
||||
noteAiVaultScanWorkChanged(args.options.signal)
|
||||
const walked = await walkInScopeSessions(args, scopePaths).finally(clearSessionScopeCwdKnowledge)
|
||||
// OMP walks Pi's buckets when it inherits PI_CODING_AGENT_DIR; the Pi parse already labels them.
|
||||
const alreadyParsedFilePaths = new Set(args.alreadyParsedFilePaths)
|
||||
for (const candidate of candidates) {
|
||||
alreadyParsedFilePaths.add(candidate.file.path)
|
||||
}
|
||||
const walked = await walkInScopeSessions({ ...args, alreadyParsedFilePaths }, scopePaths).finally(
|
||||
clearSessionScopeCwdKnowledge
|
||||
)
|
||||
if (candidates.length === 0) {
|
||||
return walked
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { resolve } from 'node:path'
|
||||
import { getAiVaultWslHomeDirs } from '../ai-vault/cached-session-list'
|
||||
import { listAiVaultSubagentSessionsInBackground } from '../ai-vault/session-scanner-background'
|
||||
import { claudeProjectsRootDirs, ompSessionsRootDirs } from '../ai-vault/session-scanner-roots'
|
||||
import { claudeProjectsRootDirs } from '../ai-vault/session-scanner-roots'
|
||||
import { sessionRowRootDirs } from '../ai-vault/session-scanner-agent-sources'
|
||||
import { isPathInsideOrEqual } from '../../shared/cross-platform-path'
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
|
||||
import type {
|
||||
@@ -32,7 +33,7 @@ export async function listAiVaultSubagentSessions(
|
||||
const roots =
|
||||
args.agent === 'claude'
|
||||
? claudeProjectsRootDirs({ wslHomeDirs })
|
||||
: ompSessionsRootDirs({ wslHomeDirs })
|
||||
: sessionRowRootDirs('omp', {}, wslHomeDirs)
|
||||
if (!roots.some((root) => isPathInsideOrEqual(resolve(root), parentFilePath))) {
|
||||
return { sessions: [], issues: [] }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { win32 } from 'node:path'
|
||||
import { GROK_CHAT_HISTORY_FILE } from '../../shared/grok-session-paths'
|
||||
import { OMP_SESSION_ARTIFACT_DIR_PATTERN } from '../ai-vault/session-scanner-omp-subagent-transcripts'
|
||||
import { isOmpConversationDirectory } from '../ai-vault/session-scanner-omp-subagent-transcripts'
|
||||
|
||||
export type SessionIdLookupAgent = 'claude' | 'codex' | 'grok' | 'omp'
|
||||
|
||||
@@ -34,6 +34,6 @@ export const SESSION_FILE_ID_LAYOUTS: Record<SessionIdLookupAgent, SessionFileId
|
||||
return name === id || name.endsWith(`_${id}`)
|
||||
},
|
||||
// Prune subagent artifact directories: their suffix can match the parent ID and steal its transcript.
|
||||
directoryPredicate: (name, depth) => depth === 0 || !OMP_SESSION_ARTIFACT_DIR_PATTERN.test(name)
|
||||
directoryPredicate: isOmpConversationDirectory
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user