Skip to content

Aquarium-as-sessions for Agent Host new-chat#316656

Draft
osortega wants to merge 3 commits into
mainfrom
agents/c879bf72-c094-4efb-a054-7175dcd23223
Draft

Aquarium-as-sessions for Agent Host new-chat#316656
osortega wants to merge 3 commits into
mainfrom
agents/c879bf72-c094-4efb-a054-7175dcd23223

Conversation

@osortega
Copy link
Copy Markdown
Contributor

What

Turns the decorative aquarium in the Agents window's new-chat view into a live 1:1 visualization of running Agent Host sessions, gated behind a hidden developer-only setting so the experience never ships to users by default.

Draft. Implementation is type-check / eslint / layer-check clean, but I have not yet exercised it in a running workbench. Marking ready once the human owner runs through the smoke tests below.

Triple-gate

Behavior activates only when all three are true:

  1. sessions.developerJoy.enabled (existing public experimental key) is true.
  2. sessions.developerJoy.aquariumAsSessions (NEW, hidden, intentionally unregistered) is true.
  3. The selected workspace or the active session matches ANY_AGENT_HOST_PROVIDER_RE (local-agent-host + agenthost-*).

To opt in, hand-edit settings.json:

{
  "sessions.developerJoy.enabled": true,
  "sessions.developerJoy.aquariumAsSessions": true
}

The aquariumAsSessions key is deliberately not in any registerConfiguration block, so it never appears in Settings UI, IntelliSense for settings.json, default-settings exports, or our docs. A schema-guard unit test (aquariumConfiguration.test.ts) asserts this invariant so we can't accidentally promote it to public.

If any of the three flips false, the original decorative aquarium and always-visible chat input behavior is restored — live, no reload.

Behavior when the gate is on

  • Chat input hidden by default. A transparent click-catcher hotspot covers the empty area with aria-label and focus-visible outline. Click, tab into, or activate the hotspot to reveal the input + workspace picker.
  • Hybrid dismiss:
    • Submit success auto-dismisses after 250 ms (0 ms when motion-reduced).
    • Outside-click dismisses, skipping .monaco-list, .monaco-action-bar, .context-view, .monaco-menu, .monaco-hover, .monaco-quick-input-widget so menus/popups don't false-dismiss.
    • Esc dismisses, but only when the input is empty — so the first press still gets Monaco's native clear behavior.
  • 1:1 fish-per-session. The new SessionPopulationDriver mirrors ISessionsManagementService.getSessions() filtered by ANY_AGENT_HOST_PROVIDER_RE. Fish color tracks status (in-progress / needs-input / completed / error) via setStatusVariant. Soft cap of 20 evicts oldest-terminal first.
  • Activity bubbles. Each fish occasionally emits a chat bubble from session.description activity text — truncated, dwell-gated, hover-locked, max 6 visible.
  • Submit grows a fish. Pressing send records a submit-intent (5 s window). The first session-fish added during that window spawns small (~18 px) and ease-out-cubic tweens to full size (~56 px) over 18 s. Reduced-motion spawns at half-grown with no tween.

Architecture

aquariumOverlay.ts's engine is split into a host + swappable IAquariumPopulationDriver. The default RandomPopulationDriver preserves today's 50-fish crowd. The new SessionPopulationDriver is attached on demand via IMountedToggleHandle.setDriverFactory(...), called from NewChatWidget whenever the triple-gate evaluation flips.

NewChatWidget adds a small collapse-mode controller (_setupAquariumModeController, _recomputeAquariumMode, _setRevealed, _installHotspot, _installAquariumDismissHandlers, _scheduleAquariumAutoDismiss) that reacts to configurationService.onDidChangeConfiguration, _workspacePicker.onDidSelectWorkspace, and an autorun on activeSession.

AquariumSubmitIntentService is a tiny singleton with recordIntent() / consumeIntent() and a 5 s window. NewChatWidget._send records the intent before awaiting sendAndCreateChat; SessionPopulationDriver._addSession consumes it the moment the new agent-host session is registered.

Telemetry

Two events:

Event Properties Purpose
aquarium.populationMode mode: 'random' | 'sessions' Adoption signal for the hidden mode. Fires on driver swap.
aquarium.chatInput action: 'reveal' | 'dismiss', trigger: 'click' | 'submit' | 'outside' | 'esc' | 'scopeChanged' Which dismiss triggers are actually used.

Owner is provisionally osortega — please confirm this is your registered GitHub handle for telemetry classifications before moving out of draft.

Files

Added (4):

  • aquariumSubmitIntentService.ts — intent bus singleton with 5 s window.
  • bubble.tsBubble class (text truncation, dwell, hover-lock, fade-out, idempotent dispose).
  • sessionPopulationDriver.ts — 1:1 driver, per-session status autoruns, soft cap.
  • aquariumConfiguration.test.ts — schema-guard test asserting the hidden key is NOT in the registered schema.

Modified (7):

  • aquariumOverlay.ts — engine/population split (IAquariumHost, IAquariumPopulationDriver, IFishHandle, IAddFishOptions, IMountToggleOptions), setDriverFactory swap, bubble layer + positioning, hidden-gate constant + reader.
  • fish.tssetTargetSize (easeOutCubic tween), setStatusVariant, _speciesColor; size no longer readonly.
  • aquarium.contribution.ts — registers IAquariumSubmitIntentService singleton; inline note that the hidden key is intentionally not registered.
  • newChatViewPane.ts — full collapse-mode controller, hotspot, hybrid dismiss, scope-reactive driver swap, telemetry.
  • newChatInput.ts — small isInputEmpty() accessor for the Esc handler.
  • aquarium.css + chatWidget.css — bubble layer, status variants, reduced-motion bubble transition, hotspot styling, focus-visible outline.

Accessibility

  • Hotspot: <button> with aria-label, natively focusable, :focus-visible outline.
  • Aquarium subtree (water, bubbleLayer, bubble, fish containers) all aria-hidden="true" (decorative).
  • Reduced-motion users: no tween on the grow-fish animation; auto-dismiss timer collapses to 0 ms; existing aquarium swim-in/out fades respect the existing reduced-motion CSS already in aquarium.css.
  • Focus restored to hotspot on dismiss-via-Esc, to input on reveal.

How to try this

  1. Add the two keys above to your settings.json.
  2. Open the Agents window's new-chat view.
  3. Select a local-agent-host workspace (or connect to an agenthost-* remote tunnel). The chat input collapses; the aquarium reveals.
  4. Click the empty area → input slides in. Tab from elsewhere → hotspot is reachable, Enter reveals.
  5. Type a query, press send → submit-fish should appear and grow. Chat input slides away after 250 ms.
  6. Wait for the session's _activity to update → its fish should occasionally pop a bubble of the text.
  7. Press Esc on an empty input, or click outside → input slides away.
  8. Flip sessions.developerJoy.aquariumAsSessions to false → behavior reverts immediately to the legacy decorative aquarium with the chat input always visible.

Validation

  • npm run compile-check-ts-native — clean.
  • npx eslint on all 11 changed/new files — clean.
  • npm run valid-layers-check — clean.
  • Schema-guard test runs in CI (needs the built out/ directory which the local worktree doesn't have).

Follow-ups before going non-draft

  • Confirm telemetry owner: 'osortega' is the right value for the GDPR classifications.
  • Manual smoke test using the steps above.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Turns the decorative aquarium in the Agents window's new-chat view into a
live 1:1 visualization of running Agent Host sessions, gated behind a
hidden developer-only setting so the experience never ships to users by
default.

Behavior is activated only when ALL THREE of the following are true (the
"triple-gate"):

  1. `sessions.developerJoy.enabled` (existing public experimental key) is `true`.
  2. `sessions.developerJoy.aquariumAsSessions` (NEW hidden, intentionally
     unregistered, key) is `true`. To opt in, hand-edit `settings.json`:

     ```json
     {
       "sessions.developerJoy.enabled": true,
       "sessions.developerJoy.aquariumAsSessions": true
     }
     ```

     The key is NOT in the configuration schema; it won't appear in Settings
     UI, IntelliSense, or default-settings exports. A schema-guard unit
     test (`aquariumConfiguration.test.ts`) asserts this invariant.

  3. The selected workspace OR the active session matches
     `ANY_AGENT_HOST_PROVIDER_RE` (`local-agent-host` + `agenthost-*`).

If any of the three flips false, the original decorative aquarium and
always-visible chat input behavior is restored — live, no reload.

When the gate is on:

* Chat input + workspace picker are hidden behind a transparent
  click-catcher hotspot. Click, tab into, or activate the hotspot to
  reveal the input. Dismiss is hybrid: auto-dismiss 250 ms after submit
  success (0 ms when motion-reduced), outside-click (skipping menus and
  popups), and Esc on an empty input.
* The aquarium renders one fish per running agent-host session. Fish
  color tracks status (in-progress / needs-input / completed / error).
  Fish appear and fade out as sessions come and go, with a soft cap of
  20 evicting oldest-terminal first.
* Each fish occasionally emits a chat bubble with that session's current
  `description` activity text — truncated, dwell-gated, hover-locked.
  Max 6 visible bubbles.
* Submitting a chat records a submit-intent (5 s window). The first
  session-fish added during that window spawns small (~18 px) and
  ease-out-cubic tweens to full size (~56 px) over 18 s. Reduced-motion
  spawns at half-grown with no tween.
* Two telemetry events fire (`aquarium.populationMode`,
  `aquarium.chatInput`) so we can see adoption + which dismiss triggers
  are used. Owner is provisionally `osortega`; confirm before moving the
  PR out of draft.

Architecture: the engine in `aquariumOverlay.ts` is split into a host +
swappable `IAquariumPopulationDriver`. The default `RandomPopulationDriver`
preserves today's 50-fish crowd; the new `SessionPopulationDriver` is
attached on demand via `IMountedToggleHandle.setDriverFactory`. The chat
widget gates the swap behind the triple-gate evaluation and reacts to
configuration + workspace-picker + active-session changes.

Files added:
  src/vs/sessions/contrib/aquarium/browser/aquariumSubmitIntentService.ts
  src/vs/sessions/contrib/aquarium/browser/bubble.ts
  src/vs/sessions/contrib/aquarium/browser/sessionPopulationDriver.ts
  src/vs/sessions/contrib/aquarium/test/browser/aquariumConfiguration.test.ts

Files modified:
  src/vs/sessions/contrib/aquarium/browser/aquarium.contribution.ts
  src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts
  src/vs/sessions/contrib/aquarium/browser/fish.ts
  src/vs/sessions/contrib/aquarium/browser/media/aquarium.css
  src/vs/sessions/contrib/chat/browser/media/chatWidget.css
  src/vs/sessions/contrib/chat/browser/newChatInput.ts
  src/vs/sessions/contrib/chat/browser/newChatViewPane.ts

Validation: tsgo type-check, eslint, and layer-check all clean.
Schema-guard test runs in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 15, 2026 17:52
@osortega osortega changed the title Aquarium-as-sessions for Agent Host new-chat (hidden developer-joy gate) Aquarium-as-sessions for Agent Host new-chat May 15, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a hidden developer-gated mode that turns the Agents window aquarium into a live visualization of Agent Host sessions, with collapsed new-chat input behavior and session activity bubbles.

Changes:

  • Adds aquarium-as-sessions gating, input reveal/dismiss behavior, and telemetry in the new-chat view.
  • Refactors aquarium population into swappable drivers and adds session-backed fish, growth, status colors, and bubbles.
  • Adds a hidden-setting schema guard test plus CSS for collapsed hotspot, fish variants, and bubbles.
Show a summary per file
File Description
src/vs/sessions/contrib/chat/browser/newChatViewPane.ts Adds aquarium-mode controller, driver switching, dismiss/reveal handling, submit intent recording, and telemetry.
src/vs/sessions/contrib/chat/browser/newChatInput.ts Adds an input-empty accessor for Esc dismiss logic.
src/vs/sessions/contrib/chat/browser/media/chatWidget.css Styles the collapsed-mode hotspot.
src/vs/sessions/contrib/aquarium/test/browser/aquariumConfiguration.test.ts Verifies the hidden aquarium setting is not registered.
src/vs/sessions/contrib/aquarium/browser/sessionPopulationDriver.ts Adds session-to-fish population, status mapping, cap eviction, growth, and bubbles.
src/vs/sessions/contrib/aquarium/browser/media/aquarium.css Adds bubble, status variant, and reduced-motion styling.
src/vs/sessions/contrib/aquarium/browser/fish.ts Adds fish resizing and status-variant support.
src/vs/sessions/contrib/aquarium/browser/bubble.ts Adds disposable activity bubble behavior.
src/vs/sessions/contrib/aquarium/browser/aquariumSubmitIntentService.ts Adds submit-intent tracking service.
src/vs/sessions/contrib/aquarium/browser/aquariumOverlay.ts Splits aquarium engine from population drivers and adds bubble hosting.
src/vs/sessions/contrib/aquarium/browser/aquarium.contribution.ts Registers the submit-intent service and documents the hidden key.

Copilot's findings

Comments suppressed due to low confidence (2)

src/vs/sessions/contrib/chat/browser/newChatViewPane.ts:262

  • The hotspot only handles click activation. Tabbing to the button focuses it but does not reveal the input, which breaks the intended keyboard/focus path for showing the composer.
		store.add(dom.addDisposableListener(button, dom.EventType.CLICK, () => {
			if (this._aquariumModeActive) {
				this._setRevealed(true, chatWidgetContainer, /*focus*/ true, 'click');
			}
		}));

src/vs/sessions/contrib/aquarium/browser/sessionPopulationDriver.ts:84

  • Incremental additions use the raw onDidChangeSessions event, while getSessions() is deduplicated by ISessionsManagementService. When duplicate local/remote agent-host sessions arrive after attach, this adds fish that getSessions() would filter out, breaking the intended 1:1 mapping to visible sessions.
			for (const added of e.added) {
				if (this._isAgentHost(added)) {
					this._addSession(added);
  • Files reviewed: 11/11 changed files
  • Comments generated: 9

Comment on lines +213 to +216
// On entering aquarium mode (or first eval) start collapsed.
if (!wasActive) {
this._setRevealed(false, chatWidgetContainer, /*focus*/ false, 'scopeChanged');
}
const bubble = new Bubble(doc, bubbleLayer, trimmed, () => {
bubblesByFishId.delete(fishId);
});
bubblesByFishId.set(fishId, bubble);
color: var(--vscode-editorHoverWidget-foreground, #d4d4d4);
border: 1px solid var(--vscode-editorHoverWidget-border, rgba(255, 255, 255, 0.1));
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
pointer-events: none;
export const BUBBLE_DEFAULT_DWELL_MS = 3500;

/** CSS-driven fade-out duration. Must match the `.agents-aquarium-bubble.fade-out` rule. */
export const BUBBLE_FADE_OUT_MS = 300;
}

isInputEmpty(): boolean {
return !this._editor?.getModel()?.getValue();
Comment on lines +459 to +461
// session is registered. The service is a no-op when the
// sessions-aware aquarium isn't active.
this.aquariumSubmitIntentService.recordIntent();

// Outside-click → dismiss. Use capture phase so we see clicks before
// they're consumed elsewhere. Skip clicks inside picker popups.
store.add(dom.addDisposableListener(targetWindow.document, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => {
Comment on lines +175 to +179
private _isAquariumModeActive(): boolean {
if (this.configurationService.getValue<boolean>(SESSIONS_DEVELOPER_JOY_ENABLED_SETTING) !== true) {
return false;
}
if (!isAquariumAsSessionsEnabled(this.configurationService)) {
* Activity bubbles and submit-grow tweens are layered on top in later phases
* (this driver only deals with population + status).
*/
export class SessionPopulationDriver extends Disposable implements IAquariumPopulationDriver {
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.

2 participants