From 5402ddfe973634cd01627bd025e00cafcdcc3c25 Mon Sep 17 00:00:00 2001 From: iliya Date: Mon, 6 Apr 2026 15:35:01 +0300 Subject: [PATCH] fix(team): narrow process teammate override --- .../services/team/TeamProvisioningService.ts | 21 +++-- src/main/services/team/runtimeTeammateMode.ts | 87 ++++++++++++++++--- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 96e955fd..2d74b6c9 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -73,7 +73,7 @@ import { TeamMetaStore } from './TeamMetaStore'; import { TeamSentMessagesStore } from './TeamSentMessagesStore'; import { TeamTaskReader } from './TeamTaskReader'; import { TeamLaunchStateStore } from './TeamLaunchStateStore'; -import { getDesktopPreferredTeammateMode } from './runtimeTeammateMode'; +import { resolveDesktopTeammateModeDecision } from './runtimeTeammateMode'; import { createPersistedLaunchSnapshot, snapshotFromRuntimeMemberStatuses, @@ -4357,7 +4357,10 @@ export class TeamProvisioningService { const { env: shellEnv, geminiRuntimeAuth } = await this.buildProvisioningEnv( request.providerId ); - const preferredTeammateMode = await getDesktopPreferredTeammateMode(request.extraCliArgs); + const teammateModeDecision = await resolveDesktopTeammateModeDecision(request.extraCliArgs); + if (teammateModeDecision.forceProcessTeammates) { + shellEnv.CLAUDE_TEAM_FORCE_PROCESS_TEAMMATES = '1'; + } let mcpConfigPath: string; try { mcpConfigPath = await this.mcpConfigBuilder.writeConfigFile(request.cwd); @@ -4388,7 +4391,9 @@ export class TeamProvisioningService { ...(request.model ? ['--model', request.model] : []), ...(request.effort ? ['--effort', request.effort] : []), ...(request.worktree ? ['--worktree', request.worktree] : []), - ...(preferredTeammateMode ? ['--teammate-mode', preferredTeammateMode] : []), + ...(teammateModeDecision.injectedTeammateMode + ? ['--teammate-mode', teammateModeDecision.injectedTeammateMode] + : []), ...parseCliArgs(request.extraCliArgs), ]; const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, { @@ -4862,7 +4867,10 @@ export class TeamProvisioningService { const { env: shellEnv, geminiRuntimeAuth } = await this.buildProvisioningEnv( request.providerId ); - const preferredTeammateMode = await getDesktopPreferredTeammateMode(request.extraCliArgs); + const teammateModeDecision = await resolveDesktopTeammateModeDecision(request.extraCliArgs); + if (teammateModeDecision.forceProcessTeammates) { + shellEnv.CLAUDE_TEAM_FORCE_PROCESS_TEAMMATES = '1'; + } let mcpConfigPath: string; try { mcpConfigPath = await this.mcpConfigBuilder.writeConfigFile(request.cwd); @@ -4907,8 +4915,8 @@ export class TeamProvisioningService { if (request.worktree) { launchArgs.push('--worktree', request.worktree); } - if (preferredTeammateMode) { - launchArgs.push('--teammate-mode', preferredTeammateMode); + if (teammateModeDecision.injectedTeammateMode) { + launchArgs.push('--teammate-mode', teammateModeDecision.injectedTeammateMode); } launchArgs.push(...parseCliArgs(request.extraCliArgs)); const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, { @@ -9660,7 +9668,6 @@ export class TeamProvisioningService { ? { CLAUDE_CONFIG_DIR: getClaudeBasePath() } : {}), CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1', - CLAUDE_CODE_ENTRYPOINT: 'claude-desktop', }; applyConfiguredRuntimeBackendsEnv(env); applyProviderRuntimeEnv(env, providerId); diff --git a/src/main/services/team/runtimeTeammateMode.ts b/src/main/services/team/runtimeTeammateMode.ts index 274cfbef..d260c3da 100644 --- a/src/main/services/team/runtimeTeammateMode.ts +++ b/src/main/services/team/runtimeTeammateMode.ts @@ -2,6 +2,14 @@ import { execFile } from 'child_process'; import { parseCliArgs } from '@shared/utils/cliArgsParser'; +const TMUX_AVAILABILITY_CACHE_TTL_MS = 10_000; + +type DesktopTeammateModeDecision = { + injectedTeammateMode: 'tmux' | null; + forceProcessTeammates: boolean; +}; + +let tmuxAvailabilityCache: { value: boolean; at: number } | null = null; let tmuxAvailablePromise: Promise | null = null; function execFileAsync(command: string, args: string[], timeout: number): Promise { @@ -16,32 +24,89 @@ function execFileAsync(command: string, args: string[], timeout: number): Promis }); } -function hasExplicitTeammateMode(rawExtraCliArgs: string | undefined): boolean { - return parseCliArgs(rawExtraCliArgs).some( - (token) => token === '--teammate-mode' || token.startsWith('--teammate-mode=') - ); +function getExplicitTeammateMode( + rawExtraCliArgs: string | undefined +): 'auto' | 'tmux' | 'in-process' | null { + const tokens = parseCliArgs(rawExtraCliArgs); + for (let i = 0; i < tokens.length; i += 1) { + const token = tokens[i]; + if (token === '--teammate-mode') { + const next = tokens[i + 1]; + if (next === 'auto' || next === 'tmux' || next === 'in-process') { + return next; + } + return null; + } + if (token.startsWith('--teammate-mode=')) { + const value = token.slice('--teammate-mode='.length); + if (value === 'auto' || value === 'tmux' || value === 'in-process') { + return value; + } + return null; + } + } + + return null; } async function isTmuxAvailable(): Promise { + if ( + tmuxAvailabilityCache && + Date.now() - tmuxAvailabilityCache.at < TMUX_AVAILABILITY_CACHE_TTL_MS + ) { + return tmuxAvailabilityCache.value; + } + if (!tmuxAvailablePromise) { tmuxAvailablePromise = execFileAsync('tmux', ['-V'], 3_000) .then(() => true) - .catch(() => false); + .catch(() => false) + .then((value) => { + tmuxAvailabilityCache = { value, at: Date.now() }; + return value; + }) + .finally(() => { + tmuxAvailablePromise = null; + }); } return tmuxAvailablePromise; } -export async function getDesktopPreferredTeammateMode( +export async function resolveDesktopTeammateModeDecision( rawExtraCliArgs: string | undefined -): Promise<'tmux' | null> { +): Promise { if (process.platform === 'win32') { - return null; + return { + injectedTeammateMode: null, + forceProcessTeammates: false, + }; } - if (hasExplicitTeammateMode(rawExtraCliArgs)) { - return null; + const explicitMode = getExplicitTeammateMode(rawExtraCliArgs); + if (explicitMode === 'tmux') { + return { + injectedTeammateMode: null, + forceProcessTeammates: true, + }; } - return (await isTmuxAvailable()) ? 'tmux' : null; + if (explicitMode === 'auto' || explicitMode === 'in-process') { + return { + injectedTeammateMode: null, + forceProcessTeammates: false, + }; + } + + if (!(await isTmuxAvailable())) { + return { + injectedTeammateMode: null, + forceProcessTeammates: false, + }; + } + + return { + injectedTeammateMode: 'tmux', + forceProcessTeammates: true, + }; }