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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user