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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//! Native emergency surfacing — the fallback for when an `emergency`
//! notification arrives but no Client App is subscribed to receive the
//! live push (KLP Phase E, #102).
//!
//! The notification bus is at-most-once (SPEC §2.12.7): a notification
//! published while no Client App is connected is normally dropped and
//! recovered later via `notifications.list`. That's fine for info/warn,
//! but an **emergency** whose whole point is to grab attention *now*
//! (SPEC §2.12.8) must not wait for the user to happen to open the app —
//! especially since the Client App does not autostart on logon, so
//! "no client connected" is a common state.
//!
//! So when [`crate::klp::notify_bus`] sees an `emergency` with zero
//! broadcast receivers, it calls [`surface_emergency`], which **launches
//! the Client App** in the active console session (via
//! [`crate::process_as_user::launch_detached_in_user_session`], the same
//! WTS token path the agent already uses for `run_as: user`), passing the
//! notification id. The launched client starts **hidden** (no window) and
//! shows only a native **toast** for the emergency — so it never
//! "bursts" over whatever the user is doing (e.g. a meeting). Clicking
//! the toast is what brings the window forward, focused on the
//! notification panel.
//!
//! We deliberately do NOT pop a `WTSSendMessageW` message box here: a
//! blocking system dialog is exactly the screen-takeover the toast design
//! avoids.
//!
//! Best-effort: the launch is fire-and-forget on a blocking thread so the
//! bus loop never stalls; a missing client install or a spawn failure is
//! a logged no-op, never propagated.
use PathBuf;
use ;
use ;
use OnTrigger;
use ;
/// Path of the installed Client App under `%ProgramFiles%` — set by the
/// `install-kanade-client` job (`<ProgramFiles>\Kanade\kanade-client.exe`).
const CLIENT_EXE_REL: &str = r"Kanade\kanade-client.exe";
/// CLI flag the launched client reads to show the emergency as a toast
/// (hidden window) instead of its normal visible startup. Kept in sync
/// with the client's arg parser (`kanade-client`'s `app.rs`).
const SHOW_NOTIFICATION_ARG: &str = "--show-notification";
/// CLI flag telling the client to **re-toast every still-unread, unexpired
/// emergency** (hidden window), bypassing its in-app duplicate-suppression —
/// used to re-pop emergencies the user couldn't see when they arrived (sent
/// while signed out, or delivered to the Action Center while the screen was
/// locked). Whether the client is launched fresh or this is forwarded to an
/// already-running instance, it re-pops. Kept in sync with `app.rs`.
const RESURFACE_ARG: &str = "--resurface";
/// Resolve the installed Client App path, or `None` when it isn't
/// installed (so the fallback is a clean no-op rather than spawning a
/// missing exe).
/// Launch throttle window: launching the heavy Tauri/WebView client is
/// expensive, so a burst of emergencies (or a logon racing a fallback) must
/// not spawn it over and over. At most one launch per window.
const COOLDOWN_SECS: u64 = 10;
static LAST_LAUNCH_SECS: AtomicU64 = new;
/// Set when an emergency was processed while the user was **not present** —
/// signed out (no console session) or signed in but **locked** — so the toast
/// either couldn't be shown or went silently to the Action Center. Re-popped
/// the next time the user becomes present (logon or unlock; see
/// [`on_session_event`]). In-memory only: an agent restart loses it, but the
/// emergency is still in the 90-day NOTIFICATIONS stream and recovers when the
/// user next opens the Client App by hand (#647).
static PENDING_RESURFACE: AtomicBool = new;
/// Whether the console session is currently **locked**. Tracked from the SCM
/// `SessionLock` / `SessionUnlock` events ([`on_session_event`]). Defaults to
/// unlocked: if the agent (re)starts while locked we won't know until the next
/// event, an accepted edge.
static LOCKED: AtomicBool = new;
/// True when an interactive user is attached to the physical console.
/// `WTSGetActiveConsoleSessionId` returns `0xFFFFFFFF` when none is (no
/// signed-in user) — in which case there's nobody to toast at.
/// True when a user can actually *see* a toast right now: signed in AND not
/// locked. When false, an emergency must be re-popped on the next presence.
/// Launch the installed Client App in the active user session, fire-and-forget
/// on a **detached OS thread** — so it works whether the caller is on the
/// tokio runtime (the notify bus) or the SCM control thread
/// ([`on_session_event`], which runs outside any runtime). `args` pass through
/// to the client; throttled to one launch per [`COOLDOWN_SECS`]. A missing
/// install or spawn failure is a logged no-op, never panics, never propagates.
/// Surface an emergency notification by launching the Client App in the user's
/// session to toast it (the **no-subscribed-client fallback**). Fire-and-forget;
/// never panics, never propagates.
///
/// Presence-aware (#647):
/// - **Signed out** (no console session) → nobody to toast at, so flag for
/// re-pop on the next logon instead of launching into the void.
/// - **Locked** → still launch (the toast lands in the Action Center as a
/// backstop) *and* flag for re-pop on unlock, since a toast that arrives
/// while locked is never actively shown.
/// - **Present** → launch and the toast shows immediately.
/// Note that an emergency was **live-pushed to an already-connected client**
/// (so [`surface_emergency`]'s fallback didn't run). If the user isn't present
/// — i.e. locked — the client's toast went silently to the Action Center, so
/// flag it for re-pop on the next presence. Called from the notify bus for
/// every emergency that reaches a subscriber.
/// React to an OS session event (#647). On **logon** or **unlock** — i.e. the
/// user becoming present — if an emergency was deferred while they were away,
/// launch the client with `--resurface` so it re-pops every still-unread,
/// unexpired emergency (`info`/`warn` stay passive by design). Also tracks the
/// lock state. A no-op when nothing was deferred.
/// If an emergency was deferred while the user was away, re-pop it now that
/// they're present.