Skip to content

Conversation

ammar-agent
Copy link
Collaborator

@ammar-agent ammar-agent commented Oct 13, 2025

Summary

Completes end-to-end "init hook streaming" infrastructure. The .cmux/init hook is now detected, executed, and its output streamed live to the UI on workspace creation. Non-zero exit codes are allowed; workspace remains usable regardless.

Changes

Backend (Electron main)

  • Remove duplicate handlers: Cleaned up 3 copies of workspace:meta:subscribe/unsubscribe handlers and malformed braces in src/services/ipcMain.ts
  • Fix type imports: Replaced inline import("@/types/workspace").WorkspaceMetaEvent with top-level import type { WorkspaceMetaEvent } to resolve eslint errors
  • Hook invocation: Already present at workspace creation (void this.runWorkspaceInitHook()) - fire-and-forget with line-buffered streaming and non-interactive env
  • Buffer and replay: metaEventBuffer stores events per workspace; late subscribers receive replay via workspace:meta:subscribe

Preload API

  • Strong typing: onMeta(workspaceId: string, callback: (data: WorkspaceMetaEvent) => void) with proper type import

Renderer (UI)

  • Typed event handling: AIView.tsx uses WorkspaceMetaEvent (no any), prefers line in error events when present, else error
  • Success auto-hide, failure persistent: Banner disappears on exit code 0, stays visible on non-zero

Types (shared)

  • Import cleanup: src/types/ipc.ts uses import type { WorkspaceMetaEvent } instead of inline import

Tests

  • Integration suite (tests/ipcMain/workspaceInitHook.test.ts):
    • ✅ Hook success: verifies start → output/error → end(0) sequence
    • ✅ Hook failure: verifies workspace remains usable on exit code 1
    • ✅ No hook: verifies no events emitted when .cmux/init absent
    • Tests poll env.sentEvents (not mockIpcRenderer.on) to detect meta events

Non-goals (can be follow-ups)

  • Memory cap for meta buffer (e.g., last N events)
  • Windows fallback (PowerShell/cmd if bash unavailable)
  • Passing project secrets to hook env

Testing

  • make lint, make fmt, make typecheck all pass
  • Integration tests pass: TEST_INTEGRATION=1 bun x jest tests/ipcMain/workspaceInitHook.test.ts
  • App builds without errors

Generated with cmux

@ammar-agent ammar-agent force-pushed the feat/init-hook branch 3 times, most recently from 84bac2e to 20740c5 Compare October 21, 2025 00:23
Complete end-to-end infrastructure for streaming .cmux/init hook output:
- Backend detects and runs optional project-level .cmux/init on workspace creation
- Stream stdout/stderr lines to renderer via WORKSPACE_STREAM_META IPC channel
- Buffer output for late subscribers; replay on subscribe
- UI displays live output with auto-hide on success, persistent banner on failure
- Workspace remains usable regardless of hook exit code

Backend (ipcMain.ts):
- Add WorkspaceMetaEvent type imports (fix inline import() eslint errors)
- Implement runWorkspaceInitHook with line-buffered streaming
- Add metaEventBuffer for replay to late subscribers
- Remove duplicate workspace:meta:subscribe handlers

Frontend (AIView.tsx):
- Add typed WorkspaceMetaEvent handler (no any types)
- Prefer line when present in error events, else error field
- Auto-hide success banner (800ms), persist failure banner

Types:
- Add WorkspaceMetaEvent union (start/output/error/end)
- Strong typing throughout preload and IPC layer

Tests:
- Add integration test suite (workspaceInitHook.test.ts)
- Verify start/output/error/end event sequence
- Verify workspace remains usable on hook failure
- Verify no events when hook absent

Generated with cmux
Simplifies init hook architecture by reusing existing chat stream
infrastructure instead of creating parallel IPC system.

Changes:
- Add WorkspaceInitEvent union to WorkspaceChatMessage (3 event types)
- Backend emits init events via AgentSession.emitChatEvent()
- Frontend handles init events from chat subscription
- Tests filter init events from chat channel
- Remove metaEventBuffer, onMeta(), and WORKSPACE_STREAM_META

Benefits:
- ~80 net LoC reduction (removed ~150, added ~70)
- 1 subscription per workspace instead of 2
- Automatic replay via existing history mechanism
- Cleaner types (no redundant workspaceId field)
- Single buffer for all workspace events

Init events are ephemeral (not persisted) and flow through the same
stream as caught-up, stream-error, and delete messages.
…ntralize bash execution

- Added workspace-init DisplayedMessage type with status tracking
- Extended StreamingMessageAggregator to convert init events to DisplayedMessage
- Created InitMessage component to render init banners in message stream
- Removed local init state management from AIView (eliminated parallel infrastructure)
- Removed legacy WorkspaceMetaEvent type (no longer used)

- Created BashExecutionService to centralize all bash execution
- Provides single abstraction point for future host migration (containers, remote, etc.)
- Eliminates duplicate environment setup across init hooks and bash tool
- executeStreaming() mode for line-by-line output (init hooks)
- Updated IpcMain to use BashExecutionService for init hook execution

Benefits:
- Init events flow through same path as other workspace events
- Centralized state management (no local component state)
- Single source of truth for bash environment setup
- Easier to abstract workspace hosts in future

Tests:
- Added unit tests for aggregator init handling (2 tests)
- All integration tests passing (3/3 init hook tests)
- Typecheck passing for both renderer and main processes
- Added log.debug() for init hook detection and script execution
- Added log.info() for init hook start and completion with exit codes
- Added log.error() for init hook failures
- Added logging to BashExecutionService for streaming command execution
- Added process error logging for bash execution failures

This improves debuggability when init hooks don't work as expected.
Init events were being buffered until "caught-up" but init hooks run during
workspace creation, before the workspace has any history or caught-up status.

Changes:
- Added isInitStart, isInitOutput, isInitEnd imports to WorkspaceStore
- Updated isStreamEvent() to include init events (process immediately)
- Added explicit init event handling in processStreamEvent()
- Init events now bypass caught-up check and process immediately

This ensures the init banner appears in the UI when workspaces are created.
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