mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-20 20:15:54 +00:00
* feat(windows): discover provider roots inside WSL distros Agents run inside a WSL distro write their history to the distro's Linux home, exposed to Windows as \\wsl$\<distro>\... and never under the user profile, so a Windows-only scan reported zero sessions for them. src/wsl.ts lists distros through %SystemRoot%\System32\wsl.exe (absolute, so nothing dropped next to the CLI can impersonate it) with a 3s timeout, decodes its UTF-16LE output, drops container-runtime distros, and enumerates each distro's home/* and root. Running distros only by default: touching \\wsl$ boots a stopped distro. CODEBURN_WSL=all|off opts in or out. Inert off win32 and memoized per process. claude and codex append <wslHome>/.claude and <wslHome>/.codex to their existing root lists, so multi-root discovery, probeRoots() and doctor cover them unchanged. Roots are additive, never an override. CODEBURN_WSL stays out of PROVIDER_ENV_VARS: cache entries are keyed by source path, so it can only add or drop paths, never make a kept entry stale, while declaring it would force a re-parse on every toggle. Fingerprints drop dev/ino for \\wsl$ paths in both caches. The 9P share synthesizes them per mount, so keying on them would re-parse every WSL session on every run; mtime+size still catches edits and appends. Refs #1059 * test(windows): cover WSL root discovery and the 9P carve-outs UTF-16 output parsing, utility-distro filtering, CODEBURN_WSL modes, UNC classification, claude/codex root injection and discovery, probeRoots, fingerprint reconciliation with zeroed dev/ino, and the win32 guard that keeps a Linux cwd from being walked on Windows. Refs #1059 * docs(windows): document WSL discovery and CODEBURN_WSL Refs #1059 * fix(windows): harden WSL discovery from adversarial review Probe \\wsl$\ before \\wsl.localhost\. The older spelling works on every WSL build; probing the newer one first sends builds without it through MUP, SMB and DNS resolution for a host named "wsl.localhost" — seconds of stall per distro, every process. Only accept lines shaped like a distro name. With nothing installed, wsl.exe prints prose on stdout whose lines otherwise read as distro names and get probed over UNC. A name must be a single token with none of the characters Windows bans from a path component and no trailing period, which discards that message in any UI language. Do not evict a \\wsl$ cache entry for being undiscovered, and serve it in write mode too. A stopped distro drops its whole root out of discovery; treating that as a deletion re-parsed everything over 9P on the next start and made totals drop while the read-only serve path still showed them. The exemption is scoped to isWslUncPath — an ordinary path whose file is gone is still evicted. Also: getCachedCodexProject was the one fingerprint site still keying on dev/ino for WSL paths; doctor now prints why it probed no WSL roots; and a distro contributing several homes labels them per user rather than letting makeUniqueLabels number them. Refs #1059 * fix(windows): re-validate running WSL distros on a TTL before UNC access wslHomes() memoized the running-distro homes for the process lifetime. In long-lived serve/menubar use a distro cached as running could be shut down and the next refresh would still touch its \\wsl$ UNC path — which can hang win32 fs calls — while a distro started after an empty discovery stayed invisible until restart. Discovery now caches for a 60s TTL only; on expiry the wsl.exe -l --running probe (timeout-bounded) re-validates the running list before any UNC path is touched, and a failed probe fails closed to native-only discovery rather than serving stale homes. Covers both shutdown and later-start transitions. Addresses the blocking review finding on PR #1062. * fix(windows): harden WSL cache reconciliation * fix(windows): bound WSL orphan refresh to the discovery TTL refreshWslHomes re-spawned wsl.exe on every full parse for a stopped-but- cached distro, ignoring the 60s TTL — up to ~3s twice per parse in the resident serve/menubar. Reuse the TTL-bounded wslHomes() probe instead; the same TTL already reflects the only transition the orphan check must catch, offline->running. * test(windows): skip on-disk POSIX-worktree walk case on real win32 The fixture builds a worktree under tmpdir() and treats it as a POSIX cwd the win32 guard should reject. On a real Windows runner tmpdir() is a C:\ path, so the guard rightly walks it and canonicalizes to the main repo, failing the assertion. The case is only constructible on a POSIX host (mocked win32); the literal /home/... test already covers the guard on Windows. --------- Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { mkdtemp, readFile, rm } from 'fs/promises'
|
|
import { tmpdir } from 'os'
|
|
import { join } from 'path'
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const statCalls = vi.hoisted(() => [] as string[])
|
|
|
|
// Keep real fs/promises behavior for the cache file while recording source
|
|
// probes. The regression must prove that flush never asks the OS about a WSL
|
|
// UNC path, because a stopped distro can make that call hang.
|
|
vi.mock('fs/promises', async () => {
|
|
const actual = await vi.importActual<typeof import('fs/promises')>('fs/promises')
|
|
return {
|
|
...actual,
|
|
stat: (...args: Parameters<typeof actual.stat>) => {
|
|
const source = String(args[0])
|
|
statCalls.push(source)
|
|
// Never touch a real share from the regression itself. If the flush
|
|
// loop regresses, fail fast instead of inheriting the Windows UNC
|
|
// timeout this test is intended to prevent.
|
|
if (/^\\\\wsl(\$|\.localhost)\\/i.test(source)) {
|
|
return Promise.reject(new Error('stubbed WSL share'))
|
|
}
|
|
return actual.stat(...args)
|
|
},
|
|
}
|
|
})
|
|
|
|
const {
|
|
clearCodexMemCaches,
|
|
codexCacheFileName,
|
|
flushCodexCache,
|
|
withCodexCacheDirectory,
|
|
writeCachedCodexResults,
|
|
} = await import('../src/codex-cache.js')
|
|
|
|
let root: string
|
|
|
|
beforeEach(async () => {
|
|
root = await mkdtemp(join(tmpdir(), 'codeburn-codex-wsl-flush-'))
|
|
statCalls.length = 0
|
|
clearCodexMemCaches()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
clearCodexMemCaches()
|
|
await rm(root, { recursive: true, force: true })
|
|
})
|
|
|
|
describe('Codex cache flush with WSL sources', () => {
|
|
it('retains WSL UNC entries without statting them, while evicting missing paths', async () => {
|
|
const cacheDir = join(root, 'cache')
|
|
const missingPath = join(root, 'missing.jsonl')
|
|
const wslPath = String.raw`\\wsl$\Ubuntu\home\alice\.codex\sessions\rollout.jsonl`
|
|
const fingerprint = { dev: 1, ino: 2, mtimeMs: 3, sizeBytes: 4 }
|
|
|
|
await withCodexCacheDirectory(cacheDir, async () => {
|
|
await writeCachedCodexResults(wslPath, 'wsl-project', [], fingerprint)
|
|
await writeCachedCodexResults(missingPath, 'native-project', [], fingerprint)
|
|
await flushCodexCache()
|
|
})
|
|
|
|
const disk = JSON.parse(await readFile(join(cacheDir, codexCacheFileName()), 'utf8'))
|
|
expect(disk.files[wslPath]).toMatchObject({ project: 'wsl-project' })
|
|
expect(disk.files[missingPath]).toBeUndefined()
|
|
expect(statCalls).toContain(missingPath)
|
|
expect(statCalls).not.toContain(wslPath)
|
|
})
|
|
})
|