Skip to content

fix(chat-mastra): emit agent lifecycle events for pane status indicators - #2450

Merged
Kitenite merged 2 commits into
mainfrom
kitenite/mastrachat-is-not-calling-hooks-like-running-done
Mar 13, 2026
Merged

Kitenite merged 2 commits into
mainfrom
kitenite/mastrachat-is-not-calling-hooks-like-running-done

Conversation

@Kitenite

@Kitenite Kitenite commented Mar 13, 2026 •

Copy link
Copy Markdown
Member

Summary

  • MastraChat runs agents in-process (not in a terminal PTY), so the shell-based hook chain that terminal agents use to notify the desktop UI via /hook/complete never fires
  • agent_start and agent_end harness events were silently swallowed — the UI never received AGENT_LIFECYCLE notifications, so pane status indicators (working/review/idle) never updated
  • Added an onLifecycleEvent callback to subscribeToSessionEvents that the desktop app wires directly to notificationsEmitter, emitting Start, Stop, and PermissionRequest events through the same path terminal agents use

Test plan

  • Open a MastraChat pane and send a message — pane indicator should show amber "working" while the agent runs
  • When the agent completes, the indicator should transition to green "review" (if tab not active) or clear to "idle" (if tab is active)
  • Verify terminal-based agent panes (Claude Code, etc.) still show indicators correctly (no regression)
  • Verify sandbox access requests show the red "permission" indicator on MastraChat panes

Summary by cubic

Emit agent lifecycle events from MastraChat so pane status indicators update correctly. In-process sessions now send Start, Stop, and PermissionRequest signals through the same notification path as terminal agents.

  • Bug Fixes
    • Added onLifecycleEvent to subscribeToSessionEvents and plumbed it through ChatMastraService.
    • Desktop router maps sessionId to paneId and emits NOTIFICATION_EVENTS.AGENT_LIFECYCLE via notificationsEmitter.
    • Exported LifecycleEvent from @superset/chat-mastra/server/trpc for desktop wiring.
    • No change to terminal-based agents; their indicators continue to work.

Written for commit c009661. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Chat service now emits lifecycle events when permission is requested, agents start, and agents stop, enabling UI updates and notifications tied to sessions/panes.
  • Public API
    • Exposes a LifecycleEvent type and an optional lifecycle callback so integrations can receive and react to these events.

MastraChat runs the agent in-process (not in a terminal), so the
shell-based hook chain that terminal agents use to call /hook/complete
never fires. This means agent_start and agent_end harness events were
silently swallowed — the desktop UI never received AGENT_LIFECYCLE
notifications to show working/review/idle pane indicators.

Add an onLifecycleEvent callback to subscribeToSessionEvents that the
desktop app wires to notificationsEmitter, emitting Start, Stop, and
PermissionRequest events directly — the same path terminal agents use.
@coderabbitai

coderabbitai Bot commented Mar 13, 2026 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a93baba2-45e3-4be7-a5af-a4f1553d977b

📥 Commits

Reviewing files that changed from the base of the PR and between 74de91a and c009661.

📒 Files selected for processing (3)
  • apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts
  • packages/chat-mastra/src/server/trpc/index.ts
  • packages/chat-mastra/src/server/trpc/service.ts

📝 Walkthrough

Walkthrough

Introduces a LifecycleEvent type and propagation path: runtime emits lifecycle events, subscribeToSessionEvents accepts an onLifecycleEvent callback, ChatMastraService accepts and forwards that callback, and the desktop router maps events to AGENT_LIFECYCLE notifications.

Changes

Cohort / File(s) Summary
Public Type Exports
packages/chat-mastra/src/server/trpc/index.ts, packages/chat-mastra/src/server/trpc/utils/runtime/index.ts
Exported new LifecycleEvent type from runtime barrels to expand public API surface.
Runtime: event emission
packages/chat-mastra/src/server/trpc/utils/runtime/runtime.ts
Added LifecycleEvent interface; subscribeToSessionEvents now accepts onLifecycleEvent?: (e: LifecycleEvent)=>void and invokes it for PermissionRequest, Start, and Stop lifecycle cases.
Service wiring
packages/chat-mastra/src/server/trpc/service.ts
ChatMastraServiceOptions gains optional onLifecycleEvent callback; passed into subscribeToSessionEvents when creating runtime.
Desktop router integration
apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts
Added resolvePaneIdFromSession and handleLifecycleEvent; initialized ChatMastraService with onLifecycleEvent: handleLifecycleEvent; exported ChatMastraServiceDesktopRouter type.

Sequence Diagram(s)

sequenceDiagram
    participant Agent as Runtime Agent
    participant Runtime as subscribeToSessionEvents
    participant Service as ChatMastraService
    participant Router as Desktop Router
    participant Emitter as notificationsEmitter

    Agent->>Runtime: emit lifecycle (PermissionRequest/Start/Stop)
    Runtime->>Runtime: build LifecycleEvent {sessionId,eventType}
    Runtime->>Service: call onLifecycleEvent(event)
    Service->>Router: onLifecycleEvent -> handleLifecycleEvent(event)
    Router->>Router: resolvePaneIdFromSession(event.sessionId)
    Router->>Emitter: emit AGENT_LIFECYCLE with paneId & eventType
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Small hops of lifecycle cheer,
Events that scurry, now appear,
From runtime burrow to desktop light,
Notifications wake the night,
I nibble code and dance outright! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: emitting agent lifecycle events for pane status indicators in MastraChat.
Description check ✅ Passed The description includes all key template sections with comprehensive details about the problem, solution, and test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kitenite/mastrachat-is-not-calling-hooks-like-running-done
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts">

<violation number="1" location="apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts:18">
P2: Don't swallow errors while resolving pane IDs; log at least a warning so lifecycle mapping failures are diagnosable.

(Based on your team's feedback about avoiding empty catch blocks that hide failures.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +18 to +20
} catch {
// App state not initialized yet
}

@cubic-dev-ai cubic-dev-ai Bot Mar 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Don't swallow errors while resolving pane IDs; log at least a warning so lifecycle mapping failures are diagnosable.

(Based on your team's feedback about avoiding empty catch blocks that hide failures.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts, line 18:

<comment>Don't swallow errors while resolving pane IDs; log at least a warning so lifecycle mapping failures are diagnosable.

(Based on your team's feedback about avoiding empty catch blocks that hide failures.) </comment>

<file context>
@@ -1,14 +1,42 @@
+				return paneId;
+			}
+		}
+	} catch {
+		// App state not initialized yet
+	}
</file context>
Suggested change
} catch {
// App state not initialized yet
}
} catch (error) {
console.warn("[chat-mastra-service] Failed to resolve paneId from sessionId", {
sessionId,
error,
});
}
Fix with Cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts`:
- Around line 24-30: handleLifecycleEvent currently emits only paneId and
eventType but consumers expect a full AgentLifecycleEvent (workspaceId, tabId,
paneId); update the code so the resolution step returns all three identifiers
(e.g., change or extend resolvePaneIdFromSession to return {paneId, workspaceId,
tabId} or add a new resolvePaneContext(sessionId) helper), then call
notificationsEmitter.emit(NOTIFICATION_EVENTS.AGENT_LIFECYCLE, { paneId,
workspaceId, tabId, eventType: event.eventType }) so
shouldSuppressForVisiblePane and handleAgentLifecycle receive workspaceId and
tabId alongside paneId. Ensure the resolver iterates panes as before and
populates workspaceId and tabId when a match is found and handle the missing
case consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 388994c9-1519-4081-92f0-c995be1b84db

📥 Commits

Reviewing files that changed from the base of the PR and between 9d4ec37 and 74de91a.

📒 Files selected for processing (5)
  • apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts
  • packages/chat-mastra/src/server/trpc/index.ts
  • packages/chat-mastra/src/server/trpc/service.ts
  • packages/chat-mastra/src/server/trpc/utils/runtime/index.ts
  • packages/chat-mastra/src/server/trpc/utils/runtime/runtime.ts

Comment on lines +24 to +30
function handleLifecycleEvent(event: LifecycleEvent): void {
const paneId = resolvePaneIdFromSession(event.sessionId);
notificationsEmitter.emit(NOTIFICATION_EVENTS.AGENT_LIFECYCLE, {
paneId,
eventType: event.eventType,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing workspaceId and tabId in emitted lifecycle event.

The handleLifecycleEvent function emits only paneId and eventType, but downstream consumers expect a complete AgentLifecycleEvent. Specifically:

  1. notification-manager.ts:shouldSuppressForVisiblePane requires workspaceId, tabId, and paneId to determine if notifications should be suppressed for visible panes (returns false early if any are missing, causing notifications to always show).
  2. handleAgentLifecycle uses workspaceId to get the workspace name for notification titles.

Since resolvePaneIdFromSession already iterates the panes, consider extracting all required fields at once.

Proposed fix to include workspaceId and tabId
-function resolvePaneIdFromSession(sessionId: string): string | undefined {
+function resolvePaneInfoFromSession(sessionId: string): {
+	paneId: string;
+	tabId: string;
+	workspaceId: string;
+} | undefined {
 	try {
 		const tabsState = appState.data.tabsState;
 		if (!tabsState) return undefined;
 		for (const [paneId, pane] of Object.entries(tabsState.panes ?? {})) {
 			if (pane.chatMastra?.sessionId === sessionId) {
-				return paneId;
+				const tab = tabsState.tabs?.[pane.tabId];
+				if (!tab) return undefined;
+				return {
+					paneId,
+					tabId: pane.tabId,
+					workspaceId: tab.workspaceId,
+				};
 			}
 		}
 	} catch {
 		// App state not initialized yet
 	}
 	return undefined;
 }

 function handleLifecycleEvent(event: LifecycleEvent): void {
-	const paneId = resolvePaneIdFromSession(event.sessionId);
+	const paneInfo = resolvePaneInfoFromSession(event.sessionId);
 	notificationsEmitter.emit(NOTIFICATION_EVENTS.AGENT_LIFECYCLE, {
-		paneId,
+		paneId: paneInfo?.paneId,
+		tabId: paneInfo?.tabId,
+		workspaceId: paneInfo?.workspaceId,
 		eventType: event.eventType,
 	});
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function handleLifecycleEvent(event: LifecycleEvent): void {
const paneId = resolvePaneIdFromSession(event.sessionId);
notificationsEmitter.emit(NOTIFICATION_EVENTS.AGENT_LIFECYCLE, {
paneId,
eventType: event.eventType,
});
}
function resolvePaneInfoFromSession(sessionId: string): {
paneId: string;
tabId: string;
workspaceId: string;
} | undefined {
try {
const tabsState = appState.data.tabsState;
if (!tabsState) return undefined;
for (const [paneId, pane] of Object.entries(tabsState.panes ?? {})) {
if (pane.chatMastra?.sessionId === sessionId) {
const tab = tabsState.tabs?.[pane.tabId];
if (!tab) return undefined;
return {
paneId,
tabId: pane.tabId,
workspaceId: tab.workspaceId,
};
}
}
} catch {
// App state not initialized yet
}
return undefined;
}
function handleLifecycleEvent(event: LifecycleEvent): void {
const paneInfo = resolvePaneInfoFromSession(event.sessionId);
notificationsEmitter.emit(NOTIFICATION_EVENTS.AGENT_LIFECYCLE, {
paneId: paneInfo?.paneId,
tabId: paneInfo?.tabId,
workspaceId: paneInfo?.workspaceId,
eventType: event.eventType,
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/chat-mastra-service/index.ts` around lines
24 - 30, handleLifecycleEvent currently emits only paneId and eventType but
consumers expect a full AgentLifecycleEvent (workspaceId, tabId, paneId); update
the code so the resolution step returns all three identifiers (e.g., change or
extend resolvePaneIdFromSession to return {paneId, workspaceId, tabId} or add a
new resolvePaneContext(sessionId) helper), then call
notificationsEmitter.emit(NOTIFICATION_EVENTS.AGENT_LIFECYCLE, { paneId,
workspaceId, tabId, eventType: event.eventType }) so
shouldSuppressForVisiblePane and handleAgentLifecycle receive workspaceId and
tabId alongside paneId. Ensure the resolver iterates panes as before and
populates workspaceId and tabId when a match is found and handle the missing
case consistently.

@github-actions

github-actions Bot commented Mar 13, 2026 •

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) ✅ View Branch
Fly.io Electric (Fly.io) ✅ View App
Vercel API (Vercel) ✅ Open Preview
Vercel Web (Vercel) ✅ Open Preview
Vercel Marketing (Vercel) ✅ Open Preview
Vercel Admin (Vercel) ✅ Open Preview
Vercel Docs (Vercel) ✅ Open Preview

Preview updates automatically with new commits

@Kitenite
Kitenite merged commit e4ef398 into main Mar 13, 2026
7 of 8 checks passed
@Kitenite
Kitenite deleted the kitenite/mastrachat-is-not-calling-hooks-like-running-done branch March 13, 2026 05:05

This branch was previously deployed

1 inactive deployment
preview — c009661e Deployed Mar 13, 2026 by Kitenite via Deploy API #2928
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