Skip to content

fix(remote): attribute remote-driven turns to the session's owning workspace - #3894

Merged
bobleer merged 4 commits into
openBitFun:mainfrom
bobleer:bob/fix-remote-session-workspace-binding
Nov 12, 2026
Merged

bobleer merged 4 commits into
openBitFun:mainfrom
bobleer:bob/fix-remote-session-workspace-binding

Conversation

@bobleer

@bobleer bobleer commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A HarmonyOS phone (Remote Connect) started a turn on a persisted desktop session that the desktop Web UI had not loaded. agentic://dialog-turn-started carried no workspace facts, so handleDialogTurnStarted created a "Remote Session" placeholder bound to the current workspace (the personal assistant workspace), while the host stored the session under its project workspace. From then on:

  • every turn/metadata save, delete_session and archive_session was refused with Session ID is already bound to another workspace;
  • the activity read never returned the session, so the row showed the CloudOff "syncing" icon indefinitely;
  • the user could neither delete nor archive the row (raw English toast only).

Seen in a user's diagnostics on 1.0.10 (Windows); no earlier fix in history.

Fix

Contract / backend

  • AgenticEvent::DialogTurnStarted gains an optional session_binding: SessionWorkspaceBinding (#[serde(default)], omitted when None). The coordinator fills it at every emit site from the loaded session's config without cloning the session.
  • The frontend projection flattens it into workspacePath, projectWorkspacePath, executionTarget, workspaceId, remoteConnectionId, remoteSshHost — the same keys session-created already uses, so the existing resolver attributes the placeholder correctly.

Web UI

  • Remote-driven placeholders are marked externalPlaceholder. For a persisted session (turnIndex > 0) the placeholder starts metadata-only, so opening it hydrates earlier turns; it then adopts the host's title, mode and binding as soon as its metadata is read, keeping the live turns it already projected.
  • A placeholder never writes its guessed title (sessionName / titleMetadata) over the persisted record. Without this, a correct binding would have let the first remote turn rename the real session to "Remote Session".
  • A placeholder created earlier without a binding (image-analysis start) is rebound when the turn start names its owner. Sessions loaded from the host are never rebound.
  • A full metadata checkpoint is skipped when the stored record cannot be read (e.g. an unreachable SSH workspace) instead of overwriting tags/title facts from an empty base.
  • A delivered approval now takes priority over the syncing state; the status mark shows its label on hover.
  • Delete / archive / rename / copy failures use localized copy (en-US, zh-CN, zh-TW) and explain a workspace binding mismatch instead of showing raw host text with storage paths. Rename failures were previously silent.

Related fixes found while reviewing remote-control robustness

  • session-created now corrects a placeholder that an earlier event (image analysis) projected before it, instead of being ignored.
  • Host relay client: a malformed frame (missing field, presence list in an unexpected shape) ended the relay loop permanently, with no disconnect event and no reconnect, leaving the host unreachable from phones and bots until restart. Such frames are now logged and dropped; the loop stops only on a newer connection epoch or a closed event receiver.

Compatibility

  • Older hosts (Peer Device, mixed versions) omit session_binding; the payload is unchanged and the frontend keeps its previous fallback. Covered by a legacy-payload deserialization test.
  • No persisted shape changes; no new Tauri command.

Remote scenarios

  • Remote control: the reported path (relay-driven turn on an unloaded session) is covered by contract, coordinator and frontend fixtures. Not exercised against a live relay or phone.
  • Peer Device: projection is shared; old-host fallback covered by fixture only.
  • Remote workspace (SSH): checkpoint-skip behavior covered by fixture only.
  • Detached Dispatch: unaffected (dispatch sessions keep their own driver).

Testing

AI-assisted; lightly tested (focused automated tests, no manual run against a device).

  • cargo test -p openbitfun-events --lib dialog_turn_started
  • cargo test -p openbitfun-core --no-default-features --features agent-runtime,git --lib user_shell_command_persists_a_standard_exec_command_tool_turn
  • cargo check --tests -p openbitfun-core -p openbitfun-agent-runtime -p openbitfun-services-integrations -p openbitfun-cli
  • cargo test -p openbitfun-services-integrations --no-default-features --features remote-connect --lib relay_client
  • pnpm --dir src/web-ui run test:run for EventHandlerModule, FlowChatStore, PersistenceModule, SessionModule, sessionNavStatus, sessionMutationFailure and NavPanel/sections/sessions
  • pnpm run i18n:audit, pnpm run type-check:web

…rkspace

A turn started from a phone or bot on a persisted session that the desktop
had not loaded created a "Remote Session" placeholder bound to whichever
workspace was current. Every later save, delete, archive and activity read
for that session was then refused by the host ("Session ID is already bound
to another workspace"), so the row showed the syncing icon forever and could
be neither deleted nor archived.

- DialogTurnStarted carries the session's workspace binding (optional, absent
  from older hosts) and the frontend projects it with the same keys as
  session-created, so the placeholder lands in its owning workspace.
- A placeholder for an already persisted session hydrates its earlier turns
  on open, adopts the host's title, mode and binding when its metadata
  arrives, and never writes its guessed title over the persisted name. A
  placeholder created earlier without a binding (image analysis) is rebound
  when the turn start names the owner.
- A full metadata checkpoint is skipped when the stored record cannot be
  read, instead of resetting tags and title facts.
- Delivered approvals take priority over the syncing state; the status mark
  shows its label on hover.
- Delete, archive, rename and copy failures use localized copy and explain a
  workspace binding mismatch instead of showing raw host text.
An image-analysis start can project a session before its creation event
arrives. The creation event now rebinds that placeholder to the owning
workspace and adopts the host record, instead of being ignored.
A relay frame missing a field, or a presence list in an unexpected shape,
ended the host's relay loop for good: no disconnect event, no reconnect,
and the host stayed unreachable from phones and bots until restart. Drop
and log such frames; stop only on a newer connection epoch or a closed
event receiver.
…ion-workspace-binding

# Conflicts:
#	src/web-ui/src/flow_chat/services/flow-chat-manager/EventHandlerModule.test.ts
@bobleer
bobleer merged commit d11438b into openBitFun:main Nov 12, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant