mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
fix(native-chat): constrain live transcript retries
This commit is contained in:
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
isGuestAbsoluteLinuxPath,
|
||||
isTranscriptPathCompatibleWithHost,
|
||||
needsWslHostTranslation,
|
||||
resetHostReadableTranscriptPathCacheForTests,
|
||||
toHostReadableTranscriptPath,
|
||||
@@ -44,6 +45,28 @@ describe('needsWslHostTranslation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('isTranscriptPathCompatibleWithHost', () => {
|
||||
it('constrains exact Windows paths to their authoritative host', () => {
|
||||
expect(
|
||||
isTranscriptPathCompatibleWithHost(ROLLOUT_UNC, { kind: 'wsl', distro: 'Ubuntu' }, 'win32')
|
||||
).toBe(true)
|
||||
expect(
|
||||
isTranscriptPathCompatibleWithHost(ROLLOUT_UNC, { kind: 'wsl', distro: 'Debian' }, 'win32')
|
||||
).toBe(false)
|
||||
expect(isTranscriptPathCompatibleWithHost(ROLLOUT_UNC, { kind: 'host' }, 'win32')).toBe(false)
|
||||
expect(
|
||||
isTranscriptPathCompatibleWithHost('C:\\Users\\ada\\x.jsonl', { kind: 'host' }, 'win32')
|
||||
).toBe(true)
|
||||
expect(
|
||||
isTranscriptPathCompatibleWithHost(
|
||||
'C:\\Users\\ada\\x.jsonl',
|
||||
{ kind: 'wsl', distro: 'Ubuntu' },
|
||||
'win32'
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toHostReadableTranscriptPath', () => {
|
||||
it('translates a WSL guest path to its UNC twin on Windows (#10326)', async () => {
|
||||
await expect(
|
||||
|
||||
@@ -31,6 +31,27 @@ export function needsWslHostTranslation(
|
||||
return platform === 'win32' && isGuestAbsoluteLinuxPath(path.trim())
|
||||
}
|
||||
|
||||
export function isTranscriptPathCompatibleWithHost(
|
||||
path: string,
|
||||
transcriptHost: NativeChatTranscriptHost,
|
||||
platform: NodeJS.Platform = process.platform
|
||||
): boolean {
|
||||
if (platform !== 'win32') {
|
||||
return true
|
||||
}
|
||||
const unc = parseWslUncPath(path)
|
||||
if (transcriptHost.kind === 'host') {
|
||||
return !unc && !needsWslHostTranslation(path, platform)
|
||||
}
|
||||
const distro = transcriptHost.distro.trim()
|
||||
return (
|
||||
distro.length > 0 &&
|
||||
(unc
|
||||
? unc.distro.toLowerCase() === distro.toLowerCase()
|
||||
: needsWslHostTranslation(path, platform))
|
||||
)
|
||||
}
|
||||
|
||||
export type HostReadableTranscriptPathDeps = {
|
||||
platform?: NodeJS.Platform
|
||||
pathExists?: (path: string) => Promise<boolean>
|
||||
@@ -135,19 +156,13 @@ export async function toHostReadableTranscriptPath(
|
||||
deps.pathExists ?? ((candidate: string) => pathExistsAsync(candidate, deps.signal))
|
||||
const platform = deps.platform ?? process.platform
|
||||
if (platform === 'win32' && deps.transcriptHost) {
|
||||
if (!isTranscriptPathCompatibleWithHost(path, deps.transcriptHost, platform)) {
|
||||
return null
|
||||
}
|
||||
const unc = parseWslUncPath(path)
|
||||
if (unc) {
|
||||
if (
|
||||
deps.transcriptHost.kind === 'host' ||
|
||||
unc.distro.toLowerCase() !== deps.transcriptHost.distro.trim().toLowerCase()
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return (await pathExists(path)) ? path : null
|
||||
}
|
||||
if (deps.transcriptHost.kind === 'wsl' && !needsWslHostTranslation(path, platform)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
// Why: classify BEFORE probing — Win32 resolves a bare `/home/…` against the
|
||||
// current drive (`C:\home\…`), so a probe first could bind chat to a local
|
||||
|
||||
@@ -117,6 +117,76 @@ describe('native chat transcript resolve polling', () => {
|
||||
subscription.unsubscribe()
|
||||
})
|
||||
|
||||
it.each([
|
||||
[
|
||||
'host provenance on WSL UNC',
|
||||
{ kind: 'host' as const },
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\ada\\session.jsonl'
|
||||
],
|
||||
[
|
||||
'wrong WSL distro provenance',
|
||||
{ kind: 'wsl' as const, distro: 'Debian' },
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\ada\\session.jsonl'
|
||||
],
|
||||
[
|
||||
'WSL provenance on a host drive',
|
||||
{ kind: 'wsl' as const, distro: 'Ubuntu' },
|
||||
'C:\\Users\\ada\\session.jsonl'
|
||||
],
|
||||
[
|
||||
'host provenance on extended WSL UNC',
|
||||
{ kind: 'host' as const },
|
||||
'\\\\?\\UNC\\wsl.localhost\\Ubuntu\\home\\ada\\session.jsonl'
|
||||
]
|
||||
])('does not fast-install %s after an initial miss', async (_name, transcriptHost, path) => {
|
||||
setPlatform('win32')
|
||||
const subscription = await subscribeNativeChatTranscript({
|
||||
agent: 'claude',
|
||||
sessionId: 'session-id',
|
||||
transcriptPath: path,
|
||||
transcriptHost,
|
||||
resolvePollIntervalMs: 10,
|
||||
onAppend: () => {}
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(mocks.install).not.toHaveBeenCalled()
|
||||
subscription.unsubscribe()
|
||||
})
|
||||
|
||||
it('preserves raw exact-path retries when provenance is unavailable', async () => {
|
||||
setPlatform('win32')
|
||||
const unc = '\\\\wsl.localhost\\Ubuntu\\home\\ada\\session.jsonl'
|
||||
const subscription = await subscribeNativeChatTranscript({
|
||||
agent: 'claude',
|
||||
sessionId: 'session-id',
|
||||
transcriptPath: unc,
|
||||
resolvePollIntervalMs: 10,
|
||||
onAppend: () => {}
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(mocks.install).toHaveBeenCalledWith(unc, expect.any(Function), expect.any(Object))
|
||||
subscription.unsubscribe()
|
||||
})
|
||||
|
||||
it('fast-installs an exact path that matches WSL provenance', async () => {
|
||||
setPlatform('win32')
|
||||
const unc = '\\\\wsl.localhost\\Ubuntu\\home\\ada\\session.jsonl'
|
||||
const subscription = await subscribeNativeChatTranscript({
|
||||
agent: 'claude',
|
||||
sessionId: 'session-id',
|
||||
transcriptPath: unc,
|
||||
transcriptHost: { kind: 'wsl', distro: 'Ubuntu' },
|
||||
resolvePollIntervalMs: 10,
|
||||
onAppend: () => {}
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(mocks.install).toHaveBeenCalledWith(unc, expect.any(Function), expect.any(Object))
|
||||
subscription.unsubscribe()
|
||||
})
|
||||
|
||||
it('keeps resolving on every retry when no exact hook path is available', async () => {
|
||||
const subscription = await subscribeNativeChatTranscript({
|
||||
agent: 'claude',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { extname } from 'node:path'
|
||||
import type { NativeChatMessage } from '../../shared/native-chat-types'
|
||||
import {
|
||||
isTranscriptPathCompatibleWithHost,
|
||||
needsWslHostTranslation,
|
||||
toHostReadableTranscriptPath
|
||||
} from './host-readable-transcript-path'
|
||||
@@ -111,9 +112,14 @@ function subscribeViaResolvePoll(
|
||||
try {
|
||||
if (exactPath && !hostReadableExactPath) {
|
||||
if (!needsWslHostTranslation(exactPath)) {
|
||||
// Non-WSL paths stay raw: installTranscriptWatcher already handles a
|
||||
// not-yet-created file, so don't spend an extra probe per tick.
|
||||
hostReadableExactPath = exactPath
|
||||
// Why: live retries must enforce the same PTY provenance as reads
|
||||
// before installing a watcher on an exact UNC or drive path.
|
||||
if (
|
||||
!args.transcriptHost ||
|
||||
isTranscriptPathCompatibleWithHost(exactPath, args.transcriptHost)
|
||||
) {
|
||||
hostReadableExactPath = exactPath
|
||||
}
|
||||
} else if (Date.now() - lastWslTranslateAt >= FALLBACK_RESOLVE_POLL_MS) {
|
||||
// Why: translating probes the UNC twin per distro over the 9P
|
||||
// share, and the guest file usually appears well after the hook does,
|
||||
|
||||
Reference in New Issue
Block a user