* feat(backend): key user shells by TaskEnvironmentID
Sessions in the same task share a TaskEnvironment. Re-key the user shell
runner so terminals are scoped per env, not per session — switching between
sessions of the same task now sees the same shell list.
- Add TaskEnvironmentID to AgentExecution / LaunchRequest / ExecutorCreateRequest
/ executor.LaunchAgentRequest, propagated through ToAgentExecution and the
orchestrator launch path.
- Add Manager.ResolveScopeKey(sessionID) — returns env when execution carries
it, falls back to sessionID for legacy paths.
- Rename runner param sessionID -> scopeID in interactive_shells.go; map keys
are now scopeID:terminalID.
- Shell handlers + WS terminal handler resolve sid -> scopeID via
ResolveScopeKey before calling the runner.
* feat(web): key dockview layout + portals to TaskEnvironmentID
Switching between sessions of the same task no longer rebuilds the dockview
layout — terminals and panels persist because layouts are env-scoped, not
session-scoped. The bottom terminal also reuses a stable sessionId across
same-env switches so it doesn't reconnect to a different shell.
- Rename layout persistence: setSessionLayout/getSessionLayout/*SessionMaximizeState
-> setEnvLayout/getEnvLayout/*EnvMaximizeState (kandev.dockview.env-layout. prefix).
- Rename dockview-session-switch.ts -> dockview-env-switch.ts; performSessionSwitch
-> performEnvSwitch with oldEnvId/newEnvId/activeSessionId.
- dockview-store.ts: currentLayoutSessionId -> currentLayoutEnvId; switchSessionLayout
-> switchEnvLayout(oldEnvId, newEnvId, activeSessionId). Same-env switches no-op.
Drop _skipLayoutSwitchForSession + consumeSkipFlag — env-keyed action handles it
structurally.
- panel-portal-manager: PortalEntry.envId; releaseBySession -> releaseByEnv.
ENV_SCOPED_COMPONENTS in dockview-desktop-layout (renamed from SESSION_SCOPED).
- useEnvSwitchCleanup replaces useSessionSwitchCleanup; tracks env changes only.
- All performLayoutSwitch callers (sidebar, dropdown, header, dialog) resolve
sid -> envId via environmentIdBySessionId before switching.
- bottom-terminal-panel: drop sessionId prop, use useEnvironmentSessionId().
- cleanupTaskStorage now takes envIds[] alongside sessionIds[] for env-scoped
layout/maximize cleanup.
* test: add coverage for env-keyed terminals + layout
Playwright e2e (terminal-env-keyed.spec.ts) — switching between sessions of
the same task keeps terminal scrollback intact and asserts both sessions
share one task_environment_id. Update the comment in
sessionless-task-switch.spec.ts to reference performEnvSwitch.
* fix: address env layout PR feedback
* test: fix flaky handshake auth check
* fix: address env scope review findings
* refactor: re-key dockview panel state by environmentId instead of sessionId
- Remove terminal from SESSION_SCOPED_COMPONENTS (uses useEnvironmentSessionId internally)
- Convert ChangesContent and file-editor-panel to useEnvironmentSessionId()
- Re-key sessionCommits store from bySessionId to byEnvironmentId
- Re-key useCumulativeDiff cache by environmentId
- Update WS handler to resolve envKey before invalidating cumulative diff cache
- Update comments across portal manager, session switch, and layout files
* fix: revert useEnvironmentSessionId in file-editor-panel
file-editor is session-scoped and ephemeral — portal is destroyed on
session switch, so useEnvironmentSessionId() has no practical effect.
* fix: address CodeRabbit review comments
- Pass appStore to setupPortalCleanup so terminal tab close still calls
stopUserShell via activeSessionId fallback (entry.sessionId is now
undefined for global panels)
- Add request version guard to useCumulativeDiff to discard stale
responses after environment switches
* fix: scope activeSessionId fallback to terminal only in portal cleanup
vscode is session-scoped and always has entry.sessionId — the
activeSessionId fallback should only apply to terminal (now global).
* fix: migrate stranded env-keyed data on registerSessionEnvironment
When registerSessionEnvironment(sessionId, environmentId) is called,
any data previously stored under the fallback sessionId key is now
migrated to the proper environmentId key. This prevents data from
"vanishing" when selectors start reading by environmentId after the
mapping is registered.
Applies to: sessionCommits, gitStatus, shell outputs/statuses,
userShells.
* fix: extract migrateEnvKeyedData helper to fix lint max-lines
* fix: address CodeRabbit review comments
- Always delete orphaned sessionId key in migrateEnvKeyedData even when
environmentId key already exists (prevents stale accumulation)
- Reset loading state when switching to an envKey with warm cache to
avoid brief spinner flash
* fix: always reset loading on envKey switch in useCumulativeDiff
Prevents stuck loading state on rapid A→B→A env switches where the
stale in-flight response is discarded by version guard but loading
is never reset.
* fix: move sync effect before fetch effect in useCumulativeDiff
React 18 batches effects in declaration order. With the sync effect
after the fetch effect, setLoading(false) from the sync overrode
setLoading(true) from the fetch on cold-cache env switches. Moving
the sync effect first ensures the fetch effect's setLoading(true)
wins the batch.
* test: add migrateEnvKeyedData coverage for registerSessionEnvironment
Tests 4 cases: normal migration, don't-clobber existing data,
no-op when sessionId equals environmentId, and partial sub-key
migration.
* fix: close stale-diff gaps in useCumulativeDiff sync effect
- Bump requestVersionRef on envKey change so in-flight fetches for
the previous envKey are discarded
- Clear loadingState[envKey] so the fetch effect isn't blocked on
re-entry (A→B→A scenario)