Skip to content

Zookeeper as a full first-party plugin that can be turned off - #13142

Draft
pierremtb wants to merge 1 commit into
codex/project-session-servicefrom
pierremtb/codex-attempt-move-zookeeper-plugin
Draft

Zookeeper as a full first-party plugin that can be turned off#13142
pierremtb wants to merge 1 commit into
codex/project-session-servicefrom
pierremtb/codex-attempt-move-zookeeper-plugin

Conversation

@pierremtb

@pierremtb pierremtb commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Stacked on #12885, which provides the project-session signal used as the non-React lifetime boundary.

Closes #12476. Part of #12583.

Why

The Zookeeper actor, WebSocket, prompt queue, reconnect logic, and file-edit bookkeeping used to be owned by the React pane. Closing the pane unmounted those owners, so an active prompt stopped and reopening required a new connection.

This moves session ownership to the Zookeeper registry runtime. There is no hidden React host or portal: pane visibility now controls only the UI, while disabling the plugin tears down the complete Zookeeper subsystem.

What changed

  • While the plugin is enabled, a registry runtime owns one controller for the active project session. It starts as soon as auth and the project editor are ready, independently of whether the pane is open, and updates auth in place.
  • A non-React controller owns the manager actor, prompt queue, reconnect and conversation persistence, billing transitions, clear-chat, interrupted-turn resume, and the file-edit workers.
  • File writes and undo-history recording moved from React hooks into serialized, disposable workers with project/editor epoch checks.
  • The pane and wrapper are now presentation adapters over the controller. While a hidden session is processing, the pane toggle replaces the sparkles icon with a spinner.

Review guide

The raw diff is larger than the behavioral change because GitHub cannot recognize hooks and callbacks that were split and converted into non-React classes. Most of the old pane/wrapper orchestration is removed and reappears behind focused interfaces.

  1. src/lib/zookeeper/registry/index.ts — start at the plugin boundary. The plugin contributes the pane runtime directly, so registry enablement activates the subsystem and registry disposal is the full off switch.
  2. src/lib/zookeeper/registry/runtime.tsx — the central lifetime policy. The runtime deliberately has no layout dependency: a ready authenticated project starts a controller even if the pane has never opened. Pane visibility and same-project readiness gaps retain it; logout, project-session replacement, a real editor replacement, or plugin disposal stop it. The per-project disposal gate prevents a replacement from overlapping an old asynchronous teardown.
  3. src/lib/zookeeper/registry/controller.ts — owns session behavior independently of React. The important additions are serialized prompt submission and revalidation of project, file, editor, and code state after asynchronous collection. Clear, resume, reconnect, and disposal invalidate stale work.
  4. ZookeeperFileRequestProcessor.ts and ZookeeperEditPatchHistory.ts — extracted file-integrity work. Undispatched stale requests are dropped; already-dispatched writes may settle, but cannot refresh or navigate a replacement project/editor. Undo history waits for both the filesystem write and the terminal exchange.
  5. ZookeeperConversationPaneWrapper.tsx and ZookeeperConversationPane.tsx — read these last. Their large deletion is intentional: mounting and unmounting them now only affects rendering.
  6. zookeeperManagerMachine.ts, SystemIO, layout, and the debug contract — small lifecycle seams for explicit socket shutdown, abortable idle waits, stale-navigation prevention, the running indicator, and the close/reopen E2E probe.

Non-obvious behavior and review focus

  • Every eligible project opens a Zookeeper connection and restores its saved conversation while the plugin is enabled, even if the pane is never opened. The React pane bundle itself remains lazy.
  • Idle connection does not count as billing usage; usage remains tied to a prompt awaiting a response.
  • Switching files within the same project preserves the actor and connection. Replacing the project session or executing editor creates a fresh session.
  • Plugin disable/re-enable waits for pending work for that project to drain before starting another controller. This deliberately favors data integrity, but a SystemIO operation that never invokes either completion callback could block same-project restart.
  • A dispatched filesystem edit is allowed to finish safely after teardown so it is not left half-accounted-for, while stale UI navigation and editor updates are suppressed. The new requestedProjectPath guard is the critical last line of defense here and deserves close review.

The most useful executable specifications are runtime.test.ts for eager activation and ownership boundaries, controller.test.ts for queue/clear/resume races, the processor/history tests for teardown during writes, and the Playwright case that asserts the actor and WebSocket identities remain unchanged across pane close/reopen. The SystemIO requestedProjectPath navigation guard does not yet have a direct regression test, and plugin disable/re-enable is covered at the runtime level rather than by a whole-app flow.

@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
modeling-app Ready Ready Preview Sep 1, 2026 8:26pm UTC

Request Review

@pierremtb pierremtb left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Some notes about the current state.

Comment on lines +536 to +540
<CustomIcon
name={icon}
className={`w-5 h-5 ${icon === 'loading' ? 'animate-spin' : ''}`}
aria-hidden
/>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Might as well :)

Comment on lines +1284 to +1285
test('closes screenshot annotation when the pane is hidden', () => {
const conversationProps = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

From Codex:

It’s a regression test for a portal-specific edge case.

Because #13142 keeps the Zookeeper React subtree mounted when its pane closes, the normal unmount cleanup no longer runs. If Zoodle screenshot annotation is active, its overlay is portaled into the viewport and would remain visible after closing Zookeeper.

The test verifies that changing isPaneVisible to false explicitly shuts down that overlay via ZookeeperConversation.tsx.

The duplicated prop fixture is ugly and could be cleaned up, but the behavior itself is necessary while we retain the portal approach.

@@ -167,6 +158,11 @@ function ZookeeperConversationPaneInner(props: AreaTypeComponentProps) {
zookeeperManagerActor,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is where we better not mess up 😬

Comment thread src/lib/zookeeper/registry/runtime.tsx Outdated
Comment on lines +65 to +67
// The registry owns the actor and transport. The portal remains a temporary
// bridge for the React-based file, history, reconnect, queue, and billing hooks.
export function createZookeeperRuntime(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this part from Codex is too complex and I'd like to make it easier to follow before marking ready.

@pierremtb pierremtb changed the title Codex: Attempt to make Zookeeper a complete first-party plugin Zookeeper as a complete first-party plugin that can be turned off Aug 21, 2026
@pierremtb pierremtb changed the title Zookeeper as a complete first-party plugin that can be turned off Zookeeper as a full first-party plugin that can be turned off Aug 21, 2026
@pierremtb
pierremtb force-pushed the pierremtb/codex-attempt-move-zookeeper-plugin branch from 00a8997 to bb7e7a6 Compare September 1, 2026 15:05
@pierremtb
pierremtb changed the base branch from main to codex/project-session-service September 1, 2026 15:07
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.

Zookeeper does not update when the user closes the pane after sending the request, and pane remains closed till completion

1 participant