mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-26 16:58:28 +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>
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest'
|
|
|
|
import { parseProviderSources } from '../src/parser.js'
|
|
import { CACHE_VERSION, computeEnvFingerprint, type CachedFile, type SessionCache } from '../src/session-cache.js'
|
|
import { setWslHomes } from '../src/wsl.js'
|
|
|
|
const WSL_HOME = '\\\\wsl$\\Ubuntu\\home\\me'
|
|
const WSL_PATH = `${WSL_HOME}\\.copilot\\session-state\\session.jsonl`
|
|
|
|
function cachedDurableFile(): CachedFile {
|
|
const timestamp = new Date().toISOString()
|
|
return {
|
|
fingerprint: { dev: 0, ino: 0, mtimeMs: Date.now(), sizeBytes: 100 },
|
|
mcpInventory: [],
|
|
turns: [{
|
|
timestamp,
|
|
sessionId: 'durable-wsl-session',
|
|
userMessage: 'hi',
|
|
calls: [{
|
|
provider: 'copilot',
|
|
model: 'gpt-4.1',
|
|
usage: {
|
|
inputTokens: 100,
|
|
outputTokens: 50,
|
|
cacheCreationInputTokens: 0,
|
|
cacheReadInputTokens: 0,
|
|
cachedInputTokens: 0,
|
|
reasoningTokens: 0,
|
|
webSearchRequests: 0,
|
|
cacheCreationOneHourTokens: 0,
|
|
},
|
|
costUSD: 0.01,
|
|
speed: 'standard',
|
|
timestamp,
|
|
tools: [],
|
|
bashCommands: [],
|
|
skills: [],
|
|
subagentTypes: [],
|
|
deduplicationKey: 'durable-wsl-call',
|
|
project: 'durable-wsl-project',
|
|
projectPath: '/home/me/durable-wsl-project',
|
|
}],
|
|
}],
|
|
}
|
|
}
|
|
|
|
afterEach(() => setWslHomes(undefined))
|
|
|
|
describe('durable provider WSL orphan reconciliation', () => {
|
|
it('keeps an active-root orphan visible when durable history retains the cache row', async () => {
|
|
setWslHomes([WSL_HOME])
|
|
const cache: SessionCache = {
|
|
version: CACHE_VERSION,
|
|
complete: true,
|
|
providers: {
|
|
copilot: {
|
|
envFingerprint: computeEnvFingerprint('copilot'),
|
|
durable: true,
|
|
files: { [WSL_PATH]: cachedDurableFile() },
|
|
},
|
|
},
|
|
}
|
|
|
|
const projects = await parseProviderSources('copilot', [], new Set(), cache)
|
|
|
|
expect(projects.map(project => project.project)).toContain('durable-wsl-project')
|
|
expect(cache.providers['copilot']?.files[WSL_PATH]).toBeDefined()
|
|
})
|
|
})
|