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
+7
View File
@@ -141,6 +141,11 @@ $activeGatewayProfile.subscribe(value => {
_lastRoutedProfile = key
})
// Target profile while a gateway swap is mid-flight (spawning/reconnecting that
// profile's backend), else null. Drives the chat's "waking up <profile>" loader
// so a lazy spawn doesn't read as a hang. Single-profile users never swap.
export const $gatewaySwapTarget = atom<string | null>(null)
let gatewaySwitch: Promise<void> | null = null
// Reconnect the single live gateway to `profile`'s backend if it isn't already
@@ -176,6 +181,7 @@ export async function ensureGatewayProfile(profile: string | null | undefined):
}
}
$gatewaySwapTarget.set(target)
gatewaySwitch = (async () => {
const desktop = window.hermesDesktop
const gateway = $gateway.get()
@@ -205,6 +211,7 @@ export async function ensureGatewayProfile(profile: string | null | undefined):
await gatewaySwitch
} finally {
gatewaySwitch = null
$gatewaySwapTarget.set(null)
}
}