aff19d0251
Phases 3-5 of the multi-connection campaign in one PR (per Teknium), on top of the registry (#86679) and composite-key backend routing (#86839). Agents from every registered connection are now usable side by side. Renderer socket registry (phase 3): - backendScopeKey moves to apps/shared (@hermes/shared) so main-process pool keys and renderer socket keys derive from ONE rule; the electron module keeps a byte-identical twin (tsconfig project boundaries) pinned by a cross-copy contract test. - store/gateway secondaries are scope-keyed: entries carry (connectionId, profile); registry-scoped entries dial through getConnectionFor + getGatewayWsUrlFor (fresh per-connect OAuth tickets against the right host); events keep the bare profile plus a connectionId tag; touch/idle keepalive uses the scope key; pruning keeps entries whose PROFILE has live work. New ensureGatewayForAgent/openGatewayForAgent fall through to the profile path for local/null sources — single-source behavior byte-identical. Union roster + plugin SDK (phases 3+4, the Bot Mode door): - hermes:agents:roster enumerates every connection's /api/profiles concurrently (eager REST, lazy sockets; unreachable sources report per-row; undialed ssh boxes stay connect-on-demand) and flattens through buildAgentRoster — the @name-device duplicate-handle rule applied once across all sources, pure + tested. - SDK: host.connections(), host.agents(), host.warmAgent(), host.ensureAgent() — feature-detected so plugins degrade cleanly on older Desktop builds. Fan-out updates (phase 5): - hermes:connections:update-all dispatches hermes update to every eligible source in parallel: local via the app's own applyUpdates pipeline, remote/ssh via the backend's own POST /api/hermes/update; cloud skipped as platform-managed (updateEligibility, pure + tested); per-connection result rows so one dead box can't wedge the batch. Settings → Connections gains the "Update all instances" button (shown with 2+ connections). Also: getJsonForBackend/postJsonForBackend helpers with the token/OAuth-cookie auth split; docs section updated from "staged rollout" to live behavior. Tests: +4 pure cases (cross-copy contract, roster handles, unreachable sources, update eligibility); FULL desktop suite 5115 passed; tsc renderer + electron + shared clean; eslint clean.
30 lines
1.1 KiB
TypeScript
30 lines
1.1 KiB
TypeScript
/**
|
|
* Composite backend scope keys for the multi-connection registry — shared
|
|
* between the Electron main process (backend pool keying) and the renderer
|
|
* (secondary socket registry), so both sides derive identical keys.
|
|
*
|
|
* The local/primary connection keeps the BARE profile key: every legacy pool
|
|
* entry, reaper log line, and touch call stays byte-identical for
|
|
* single-source users. Non-local connections get `conn:<id>::<profile>`,
|
|
* which cannot collide with a plain profile name (colons are invalid in
|
|
* profile names).
|
|
*/
|
|
|
|
export const LOCAL_CONNECTION_ID = 'local'
|
|
|
|
export function backendScopeKey(connectionId: null | string | undefined, profile: null | string | undefined): string {
|
|
const profileKey = String(profile ?? '').trim() || 'default'
|
|
const connection = String(connectionId ?? '').trim()
|
|
|
|
if (!connection || connection === LOCAL_CONNECTION_ID) {
|
|
return profileKey
|
|
}
|
|
|
|
return `conn:${connection}::${profileKey}`
|
|
}
|
|
|
|
/** All pool keys owned by a connection share this prefix (teardown on remove). */
|
|
export function backendScopePrefix(connectionId: string): string {
|
|
return `conn:${String(connectionId).trim()}::`
|
|
}
|