diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index f86b2ff1..3306ad81 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -6541,11 +6541,16 @@ export class TeamProvisioningService { ): Promise { const settingsPath = path.join(projectCwd, '.claude', 'settings.local.json'); try { - const added = await this.addPermissionRulesToSettings( - settingsPath, - [...AGENT_TEAMS_NAMESPACED_TEAMMATE_OPERATIONAL_TOOL_NAMES], - 'allow' - ); + // FACT: Teammates need both MCP tools AND standard file tools (Write/Edit). + // FACT: Standard tools use "setMode: acceptEdits" permission_suggestions, but + // we can't change subprocess session mode — so we pre-add them as allow rules. + const allTools = [ + ...AGENT_TEAMS_NAMESPACED_TEAMMATE_OPERATIONAL_TOOL_NAMES, + 'Edit', + 'Write', + 'NotebookEdit', + ]; + const added = await this.addPermissionRulesToSettings(settingsPath, allTools, 'allow'); logger.info( `[${teamName}] Seeded teammate operational MCP rules in ${settingsPath} (${added} added)` ); diff --git a/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts b/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts index e0b4598f..89b2a36d 100644 --- a/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts +++ b/src/renderer/features/agent-graph/adapters/TeamGraphAdapter.ts @@ -8,6 +8,7 @@ */ import { agentAvatarUrl } from '@renderer/utils/memberHelpers'; +import { getInboxJsonType, isInboxNoiseMessage } from '@shared/utils/inboxNoise'; import { isLeadMember } from '@shared/utils/leadDetection'; import type { @@ -524,6 +525,15 @@ export class TeamGraphAdapter { // Skip comment notifications — #buildCommentParticles handles them with real text if (msg.summary?.startsWith('Comment on ')) continue; + // Handle noise messages: idle shows as "idle", others (shutdown, terminated) skip entirely + const msgText = msg.text ?? ''; + const noiseType = getInboxJsonType(msgText); + if (noiseType === 'idle_notification') { + // Show idle as a simple label, don't skip + } else if (isInboxNoiseMessage(msgText)) { + continue; // skip shutdown_approved, teammate_terminated, shutdown_request + } + const edgeId = TeamGraphAdapter.#resolveMessageEdge(msg, teamName, leadId, leadName, edges); if (!edgeId) continue; @@ -537,13 +547,19 @@ export class TeamGraphAdapter { ); const isFromTeammate = fromId !== leadId; + // For idle notifications, show a clean "idle" label instead of raw JSON + const particleLabel = + noiseType === 'idle_notification' + ? 'idle' + : TeamGraphAdapter.#buildParticleLabel(msg.summary ?? msg.text, 'inbox'); + particles.push({ id: `particle:msg:${teamName}:${msgKey}`, edgeId, progress: 0, kind: 'inbox_message', color: msg.color ?? '#66ccff', - label: TeamGraphAdapter.#buildParticleLabel(msg.summary ?? msg.text, 'inbox'), + label: particleLabel, reverse: isFromTeammate, }); } @@ -735,7 +751,7 @@ export class TeamGraphAdapter { static #buildParticleLabel( text: string | undefined, kind: 'inbox' | 'comment', - max = 26 + max = 52 ): string | undefined { const normalized = text?.replace(/\s+/g, ' ').trim(); const prefix = kind === 'comment' ? '\u{1F4AC}' : '\u{2709}';