feat(env): per-workspace PATH preset injected at spawn (T-511)
Implements D-106. The T-439 login-shell probe is a global heuristic with a known hole — login-but-non-interactive shells skip ~/.bashrc, so interactive-only additions (brew shellenv) never reach the agent's Bash tool or terminal panes on a desktop launch. The preset is the explicit per-repo layer on top: user-scope storage keyed by repo identity (a linked worktree resolves through its gitdir pointer to the main repo, so worktrees share the preset), prepended at spawn via the PaneRegistry pathForSpawn hook and agentEnvDelta prependDirs — which now exports PATH even when clide is already resolvable, closing the gap where the hosted session inherited the sparse GUI PATH untouched. CLI half: `clide env path list|set|add|remove|clear|capture` over an injected Flutter-free store port; capture diffs the login-shell PATH against the process PATH to suggest the dirs a desktop launch dropped. Binary resolution (toolchain, supporter pins, bundled pql/git) stays preset-blind per the D-92/T-98 fence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,33 @@ void main() {
|
||||
final d = agentEnvDelta(workspaceRoot: '/repo', socketPath: '/s.sock', currentPath: null, clideCliDir: '/opt/clide/bin');
|
||||
expect(d['PATH'], '/opt/clide/bin');
|
||||
});
|
||||
|
||||
test('exports PATH for a preset even when clide is already resolvable (D-106)', () {
|
||||
final d = agentEnvDelta(workspaceRoot: '/repo', socketPath: '/s.sock', currentPath: '/usr/bin:/bin', clideCliDir: null, prependDirs: ['/opt/go/bin']);
|
||||
expect(d['PATH'], '/opt/go/bin:/usr/bin:/bin');
|
||||
});
|
||||
|
||||
test('preset dirs come first, then the cli dir, then the current PATH (D-106)', () {
|
||||
final d = agentEnvDelta(
|
||||
workspaceRoot: '/repo',
|
||||
socketPath: '/s.sock',
|
||||
currentPath: '/usr/bin',
|
||||
clideCliDir: '/home/dev/.local/bin',
|
||||
prependDirs: ['/opt/go/bin', '/brew/bin'],
|
||||
);
|
||||
expect(d['PATH'], '/opt/go/bin:/brew/bin:/home/dev/.local/bin:/usr/bin');
|
||||
});
|
||||
|
||||
test('a preset dir already on the current PATH is not duplicated (D-106)', () {
|
||||
final d = agentEnvDelta(
|
||||
workspaceRoot: '/repo',
|
||||
socketPath: '/s.sock',
|
||||
currentPath: '/opt/go/bin:/usr/bin',
|
||||
clideCliDir: null,
|
||||
prependDirs: ['/opt/go/bin'],
|
||||
);
|
||||
expect(d['PATH'], '/opt/go/bin:/usr/bin');
|
||||
});
|
||||
});
|
||||
|
||||
group('resolveClideCliDir (T-215)', () {
|
||||
@@ -143,5 +170,16 @@ void main() {
|
||||
final b = agentBootstrap('/ws', base: {'CLAUDE_CONFIG_DIR': '/override'}, boundConfigDir: (_) => '/bound');
|
||||
expect(b.envDelta['CLAUDE_CONFIG_DIR'], '/override');
|
||||
});
|
||||
|
||||
test('the workspace PATH preset lands at the head of the delta PATH (D-106)', () {
|
||||
final b = agentBootstrap('/ws', pathPreset: (cwd) => cwd == '/ws' ? ['/opt/go/bin'] : const []);
|
||||
expect(b.envDelta['PATH'], startsWith('/opt/go/bin:'));
|
||||
});
|
||||
|
||||
test('no preset wired → bootstrap behaves as before (no gratuitous PATH export)', () {
|
||||
final emptyPreset = agentBootstrap('/ws', pathPreset: (_) => const []);
|
||||
final unwired = agentBootstrap('/ws');
|
||||
expect(emptyPreset.envDelta.containsKey('PATH'), unwired.envDelta.containsKey('PATH'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user