Files
hermes-agent/apps
Zeus-Deus cb75983abf fix(desktop): profile-rail fresh chats keep their registry source as the exact owner
The Sessions / profile-rail path (selectProfile, newSessionInProfile, a
connection switch, `/profile`) sets $newChatProfile and deliberately clears
$newChatRoute, so a fresh chat had no explicit owner. The session was created
on the active registry gateway (conn:local::omar) but its durable owner
degraded to the bare string "omar": follow-up RPCs dialed
requestGatewayForProfile("omar") — a different socket than the one that
minted the WebSocket-scoped runtime — and 4001'd "session not found" while
the runtime was ws-orphan-reaped.

- store/profile: capture the active registry source together with the
  new-chat profile intent ($newChatConnectionId / captureNewChatSource) in
  selectProfile, newSessionInProfile, newSessionInAgent, connection switches
  and `/profile`; resolveNewChatOwnerRoute() derives the exact
  { connectionId, profile } route whenever a registry source is live, even
  with $newChatRoute null (legacy v1 primary still yields null).
- use-session-actions: session.create, the owner hint, the optimistic row's
  profile + connection_id, and the failed-create cleanup all use that
  effective owner (main chat and tile paths).
- use-prompt-actions/submit: re-pin targetStoredSessionId after a fresh
  create. It was captured before the create (null) and seedOptimistic handed
  it to updateSessionState, which the state cache read as a DETACH — the
  fresh stored↔runtime binding was severed the moment the chat existed, so
  every later session-scoped RPC failed to translate the runtime id, never
  saw the tile route / owner hint / row, probed REST by runtime id and fell
  to the ambient socket.
- contrib: the session-RPC dispatcher is factored out of wiring.tsx
  (createSessionRpcDispatcher) so the exact production routing is what the
  integration test drives.

Regression (profile-rail-fresh-chat-owner.test.tsx) drives the real path:
mocked sockets under the real registry store, primary = remote default,
active source = local, selectProfile("omar") ($newChatProfile = "omar",
$newChatRoute = null), real useSessionStateCache / useSessionActions /
usePromptActions and the production dispatcher; asserts session.create and
BOTH prompt.submit calls hit the same conn:local::omar gateway object, no
session-scoped RPC reached the primary or a v1 "omar" socket, no
session.close, the binding survives both turns, no REST probe.

Verified with the packaged Linux Desktop against the real ~/.hermes
(primary = remote OAuth gateway, "This device" as registry source, omar via
the profile rail, two prompts): both prompts persisted on one session in
profiles/omar/state.db, no ws_orphan_reap.

Refs #94071

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 03:22:37 -07:00
..