feat(desktop): drag sessions into chat as @session links + spawn loader

Drag a sidebar session into the composer to drop an @session:<profile>/<id>
chip the agent resolves via session_search. New READ shape dumps a whole
session by id (head+tail when large); a `profile` param reads another
profile's DB read-only, and a cross-profile locate scan resolves bare ids
when the model drops the owning profile from the link.

Also: ASCII "waking up <profile>" overlay during lazy gateway swaps,
global haptic rate-limit to kill the reconnect-storm "clickity" buzz, and
reauth toasts surfaced once per disconnect instead of every backoff tick.
This commit is contained in:
Brooklyn Nicholson
2026-06-04 19:41:51 -05:00
parent a40e20e136
commit 9dbd3c57d7
15 changed files with 604 additions and 45 deletions
+17
View File
@@ -87,6 +87,15 @@ export type HapticTrigger = (input?: HapticInput, options?: TriggerOptions) => P
let registeredTrigger: HapticTrigger | null = null
let lastSelectionAt = 0
// Global rolling rate-limit. A runaway upstream loop (auth-expiry error-toast
// storms, reconnect flaps) can request dozens of haptics a second, which the
// trackpad actuator renders as a frantic "clickity" buzz. Cap firings to
// RATE_LIMIT per RATE_WINDOW so no source can machine-gun the actuator;
// intentional UI haptics are human-paced and never approach the ceiling.
const RATE_WINDOW = 1000
const RATE_LIMIT = 5
let recentFires: number[] = []
export function registerHapticTrigger(trigger: HapticTrigger | null) {
registeredTrigger = trigger
}
@@ -106,6 +115,14 @@ export function triggerHaptic(intent: HapticIntent = 'selection') {
lastSelectionAt = now
}
recentFires = recentFires.filter(t => now - t < RATE_WINDOW)
if (recentFires.length >= RATE_LIMIT) {
return
}
recentFires.push(now)
const config = HAPTIC_INTENTS[intent]
void registeredTrigger(config.pattern, config.options)?.catch(() => undefined)