1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! The `PATH` a GUI launch does not get.
//!
//! Agents are named by command rather than by path — `npx`, or an installed
//! shim whose first line is `#!/usr/bin/env node` — and
//! [`super::acp::Session::spawn`] execs that name against this process's
//! environment. Started from a terminal that environment is the shell's and
//! every one of them resolves. Started from Finder, the Dock, or `open`,
//! launchd hands the app `/usr/bin:/bin:/usr/sbin:/sbin` and nothing else: no
//! homebrew, no nvm, no cargo. The agent that runs all through development
//! cannot start in the bundle, and neither can anything its own tools reach
//! for afterwards, because the child inherits whatever we were handed.
//!
//! So ask the login shell what the PATH is, once, before anything is spawned.
use ;
/// How long the shell is given to answer. A shell still thinking after this
/// is one whose startup files are doing something that cannot be waited on at
/// launch — the inherited PATH is the answer then, which is the answer an app
/// without any of this has.
const TIMEOUT: Duration = from_secs;
/// How often it is looked in on while it runs.
const TICK: Duration = from_millis;
/// Put the login shell's `PATH` on this process.
///
/// Call from `main` while it is still the only thread. Writing the
/// environment is unsound the moment a second one might be reading it, and
/// every reader in this app — every agent spawn, every `npm` the installer
/// runs — comes later.
///
/// Silent on every failure, because each one leaves the PATH we were launched
/// with and that is no worse than not having asked.
/// Run the shell the way a terminal would and take the PATH out of what it
/// ends up with. Login *and* interactive: `.zshrc` is where nvm and the rest
/// install themselves, and a login shell on its own never reads it.
///
/// `env` rather than `echo $PATH` so the answer survives the shell it came
/// from. fish holds `PATH` as a list and would hand back a space-separated
/// line; the exported variable `env` prints is colon-separated in every shell
/// there is.
/// The `PATH=` line of an `env` dump — the last one, because a startup file
/// that prints a banner prints it before `env` ever runs.
/// The shell's PATH, then anything the inherited one had that it did not.
///
/// A merge and not a replacement: this only ever runs on a launch with
/// nothing to lose, but a PATH somebody put there on purpose — a wrapper
/// script, a test harness — is still not ours to drop.